零成本邮件验证码全链路拆解,附最新 QQ 邮箱 SMTP 授权码完整步骤

简介: 本文详解个人项目“词帆CiFan”中邮件验证码功能的完整实现:选用QQ邮箱SMTP零成本方案,涵盖密码重置与账号注销两大核心场景,包含前后端链路设计、Redis降级容错、安全配置避坑(如授权码获取、端口选择、环境变量保护)及AI辅助开发技巧。

                                             

我在开发词帆 CiFan 到用户系统的时候,有两个功能绕不开:密码重置和账号注销。

它们背后其实都依赖同一件事——邮件验证码

突然想和大家分享,为什么呢?

因为大家在vibe coding 的时候来开发项目,当别人来注册以及忘记密码绕不开一个东西 — 邮箱。

邮箱能干嘛?

当客户忘记了密码,可以通过邮箱找到来重置密码……

如果是手机号来验证就要花费点费用,因为是个人开发,能省则省。

我使用的是QQ邮箱,具体的实现流程

文中末尾提供了最新的QQ邮箱如何生成 SMTP 的授权码

使用本文技巧:

把我的这篇文章和看第6点的方法获取授权码,一起给到ai,就能够实现这个功能了😊
作者:艺杯羹

文章目录


1. 为什么不用第三方邮件服务

决定技术方案的时候,摆在面前的选择大致是这几条路:SendGrid、阿里云邮件推送、Resend,或者直接用 QQ 邮箱的 SMTP。

前几个都得实名认证、绑域名,Resend 免费额度每天 100 封倒是挺慷慨,但毕竟多一层外部依赖,万一哪天改了定价或者停了免费额度,整个用户系统的安全链路就断了。

QQ SMTP 的思路很直接:拿一个 QQ 邮箱,去设置里开启 SMTP 服务拿到授权码,Spring Boot 的 JavaMailSender 原生支持,一行 Maven 依赖就够了。

零额外成本,零额外服务,自己完全可控。缺点也有——每天有发送量上限,但对于一个还没到十万用户的独立项目来说,绰绰有余。

算清楚利弊之后,选了 QQ SMTP。


2. 密码重置:从前端三步到后端一条链路

先说最核心的场景:用户忘了密码。

前端这边,整个交互拆成三步,每一步只做一件事,避免一个页面塞太多东西。

2.1. 输入邮箱

用户从登录页点"忘记密码?"进来,第一眼看到的就一个输入框和一个按钮。

其实登录页这里有个细节:登录的时候支持用户名和邮箱两种方式,如果输入的内容里带了 @,后端自动识别为邮箱登录。

所以用户注册时填的邮箱,本身就是一套备用的身份标识。

到了忘记密码页,用户输入邮箱后点"发送验证码",前端会先拦一道——正则校验邮箱格式,不合规就当场提示,没必要走一次网络请求等后端报错。

2.2. 等验证码

点击发送之后,按钮自动进入 60 秒冷却倒计时,文案从"发送验证码"变成"XX 秒后重新发送"。

这个 60 秒的限制纯粹是前端控的,后端其实没做发送频率限制。

但如果以后用户量上来,Redis 里加一个 email:rate:{邮箱} 的计数器是最简单的防刷方案,每发一次+1,超过阈值就拒绝,五分钟过期。

同一时刻后端在做什么?控制器接到请求后走了三条逻辑:

校验邮箱——正则再过一遍,然后查数据库看这个邮箱有没有注册过。没注册的邮箱直接返回错误,不暴露"此邮箱未注册"这种信息泄露的提示,统一用"验证码已发送"兜底。② 生成验证码——ThreadLocalRandom 生成 6 位纯数字,比 Random 更安全一点,并发场景下也不会出现相同种子的问题。③ 存储 + 发送——验证码存进 Redis,key 的格式是 email:code:用户邮箱,过期时间 5 分钟。然后调 MailService.sendVerificationCode() 走 JavaMailSender 推送到 QQ SMTP。

这里有一个容易被忽略的点:Redis 挂掉怎么办?代码里加了一层 ConcurrentHashMap 内存降级。

Redis 不可用的时候自动切到本地内存存储,虽然服务重启后验证码会丢,但至少比直接报 500 让用户看到一堆红字要体面得多。

2.3. 重置密码

用户输入 6 位验证码,再设一个新密码(最少 6 位,两次输入要一致),点提交。

后端验证逻辑也是一条直线:

从 Redis(或内存 Map)里取出验证码 → 比对 → 不对就返回错误 → 对的话从 Redis 里立刻删掉这个 key,防止二次使用 → 然后查用户、BCrypt 加密新密码、更新数据库。

验证码用完即删这个设计,是防止一个码被反复利用。

5 分钟过期是兜底,即时销毁才是主要防线。

走完这三步,前端接到成功响应后直接跳转 /login,用户用新密码登录就行。


3. 账号注销:多一道安全门

注销功能上线之前,我犹豫过要不要加验证。

最后加了,原因很简单:密码登录本身是一道锁,但如果用户登录态还在(比如在公共电脑上忘了退出),任何人都能直接点注销把账号删了。

所以注销弹窗里给了两个选项:密码验证邮箱验证码验证,用户选一个。密码验证就是输入当前密码,BCrypt 比对;

邮箱验证码验证跟重置密码的逻辑几乎一样,走 POST /api/v1/user/deactivate/code 发送验证码(这个接口是 JWT 保护的,没登录调不了),验证通过后执行注销。

注销邮件和重置密码邮件的模板稍有不同,主题变成了"注销账号安全校验",正文加了一句警告提示:“注销为不可逆操作,所有数据将被永久删除”。写邮件模板的时候每个字都得想清楚,特别是这种不可逆操作的提示。


4. 后端架构:一层接一层,但每一层只干一件事

把后端邮件相关的组件摊开看:

AuthControllerUserController 负责接收请求、参数校验、返回统一响应格式。它们不直接操作邮件,而是调了两个 Service:MailService 管发送,VerificationCodeService 管验证码的存取和校验。

MailServiceImpl 里就一个核心方法,注入 JavaMailSender,拼 MimeMessage——发件人名称固定为 `` 【词帆 CiFan】``,主题和正文通过参数传入。验证码塞在正文里,格式是 `` 您的重置密码验证码是: {code},有效期为 5 分钟`。

降级逻辑也在这层。如果 application.yml 里的 spring.mail.username 为空或者根本没配,直接 log.info("发送给 {} 的验证码: {}", to, code) 打到控制台,不会抛异常。开发调试的时候每次切回 IDE 的控制台就能看到验证码,不用真的发一封邮件。

VerificationCodeServiceImpl 管存取,优先用 RedisTemplate,catch 到异常就切 ConcurrentHashMap。两个 key 相关的操作——生成(storeCode)和校验并销毁(verifyCode)——都在同一个类里,没有散落到各处,改动的时候只改一个文件。


5. 配置环节踩过的坑

5.1. SMTP 端口

QQ 邮箱 SMTP 支持两个端口:587(STARTTLS)和 465(SSL)。

一开始我配了 465,加上 spring.mail.properties.mail.smtp.ssl.enable=true,结果本地连不上。排查后发现 QQ 的 465 端口在某些网络环境下会超时,换成 587 + STARTTLS 就稳了。587 是标准邮件提交端口,移动端和宽带网络兼容性都更好。

5.2. 授权码 ≠ 登录密码

这个坑很多人踩过。QQ 邮箱的 SMTP 密码不是 QQ 登录密码,是去邮箱设置 → 账户 → POP3/IMAP/SMTP 服务里生成的"授权码",16 位。一旦开启 IMAP/SMTP 服务后页面就不显示完整授权码了,建议第一次生成就存下来。

![截图⑧:application-dev.yml 中 mail 配置区域,password 字段打马赛克]

5.3. 环境变量与 Git 安全

开发环境的配置放在 application-dev.yml 里,包含了 SMTP 账户和授权码。如果提交到公开仓库,别人拿到授权码就能用你的 QQ 邮箱发任意内容的邮件。所以生产部署的时候,application.yml 里用占位符:

mail:
  username: ${SPRING_MAIL_USERNAME:}
  password: ${SPRING_MAIL_PASSWORD:}

然后在 Docker Compose 里从 .env 文件注入,或者直接在服务器的环境变量里配。开发环境的 application-dev.yml.env 加进 .gitignore

另外,部署后不要忘了改掉 QQ 邮箱的默认发件人姓名。QQ 邮箱设置里可以自定义发件人名称,设成品牌名(比如"词帆 CiFan"),用户收到的邮件就不会显示一串 QQ 号。

6. QQ配置SMTP

进入QQ邮箱https://wx.mail.qq.com/

单击右上角的设置 —> 单击左侧的“账号和安全”

在“账号设置”往下滑 —> 单击生成授权码按照流程,就能够生成授权码

随后将我的文章和授权码给 vibe coding 的工具(cursor、trae…)即可。

这样ai就能够根据这个流程来开发这里的功能了!

7. 最后的话

最开始选型的时候,我也看过不少成熟的第三方邮件服务,功能全、稳定性强,但要么有额度门槛,要么依赖外部服务。

对一个还在冷启动的个人项目来说,把链路攥在自己手里,零成本就能跑通全流程,比什么都实在。

一个看似简单的验证码功能,从前端的 60 秒倒计时,到后端的校验、存储、发件,再到 Redis 挂掉的兜底方案,前前后后碰到了不少问题。

但当自己亲手把整条链路调通,收到测试邮件的那一刻,那种踏实感是直接调用第三方 API 给不了的。

目录
相关文章
人工智能 缓存 前端开发
12026 63
人工智能 JavaScript 开发工具
4812 17
Web App开发 人工智能 API
1385 1
人工智能 Java BI
1472 1
开发工具 Swift git
1974 6
人工智能 JavaScript 测试技术
2406 2
人工智能 自然语言处理 安全
992 0
人工智能 JavaScript 测试技术
1200 4
缓存 JavaScript Shell
2102 3

热门文章

最新文章