SpringBoot + Redis 实现邮箱验证码发送
邮箱验证码几乎是所有系统都需要使用的功能,例如:
- 用户注册
- 找回密码
- 登录验证
- 修改邮箱
- 二次身份验证
很多初学者都会犯一个错误:发送验证码后直接把验证码返回给前端。
例如:
json
{
"code":"123456",
"msg":"success"
}这种方式虽然方便测试,但是在真实业务中存在严重安全风险。
正确的做法应该是:
服务端生成验证码 → 发送邮件 → 将验证码缓存到 Redis → 前端仅收到发送成功提示 → 用户提交验证码时由服务端完成校验。
本文将介绍这种生产环境推荐方案。
整体流程
整个发送流程如下:
text
客户端
│
│ 请求发送验证码
▼
SpringBoot
│
├── 生成6位验证码
│
├── 写入 Redis(60秒)
│
├── 渲染 HTML 邮件
│
└── QQ SMTP发送邮件
│
▼
用户邮箱
-------------------------
用户输入验证码
│
▼
SpringBoot
│
从 Redis 获取验证码
│
比较是否一致
│
成功
▼
继续注册/登录业务整个过程中,验证码始终不会暴露给前端。
QQ邮箱开启 SMTP 服务
首先需要开启 QQ 邮箱 SMTP 服务。
进入:
设置 → 账户
开启
POP3/SMTP服务完成验证以后,会获得一串 授权码。
例如:
xxxxxxxxxxxxxxxx注意:
这里不是QQ密码,而是SMTP授权码。
Maven 依赖
项目需要引入邮件发送依赖以及 Freemarker 模板引擎。
xml
<!-- 邮件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<!-- 模板引擎 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<!-- Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>邮箱配置
配置 QQ SMTP。
yaml
spring:
mail:
host: smtp.qq.com
port: 587
username: xxxx@qq.com
password: xxxxxxxxxxxx
protocol: smtp
properties:
mail:
smtp:
auth: true
starttls:
enable: true
data:
redis:
host: localhost
port: 6379邮件模板
推荐将 HTML 模板放到:
resources/templates/mailtemplate.ftl模板可以使用 Freemarker 渲染验证码:
html
验证码:
<span style="font-size:30px;color:#3b82f6;">
${code}
</span>
五分钟内有效。相比 MessageFormat,占位符更加直观,也更符合 Freemarker 的使用方式。
Redis 缓存验证码
生成验证码以后,不再返回前端,而是写入 Redis。
例如:
java
String code = RandomUtil.randomNumbers(6);
redisTemplate.opsForValue().set(
"email:code:" + email,
code,
Duration.ofMinutes(5)
);Redis 中的数据如下:
Key
email:code:123@qq.com
Value
482913
TTL
300s这样验证码将在五分钟后自动失效,无需手动删除。
邮件发送
邮件发送流程保持不变。
主要步骤:
- 创建 MimeMessage
- 渲染 HTML 模板
- 设置收件人
- 设置主题
- JavaMailSender 发送邮件
发送成功以后,仅返回:
json
{
"success": true,
"message": "验证码发送成功"
}不会返回验证码。
验证码校验
用户输入验证码后,调用校验接口。
例如:
POST
/email/verify请求:
json
{
"email":"123@qq.com",
"code":"482913"
}服务端流程:
java
String redisCode =
redisTemplate.opsForValue()
.get("email:code:" + email);
if(redisCode == null){
return "验证码已过期";
}
if(!redisCode.equals(code)){
return "验证码错误";
}
redisTemplate.delete("email:code:" + email);
// 执行业务验证成功后建议立即删除 Redis 中的验证码,防止重复使用。
Redis Key 设计
推荐统一管理验证码 Key。
text
email:code:{email}例如:
email:code:123@qq.com如果还有短信验证码,可以继续扩展:
sms:code:13800138000如果还有登录验证码:
login:code:userId统一命名更加方便维护。
接口设计
发送验证码:
POST /email/send参数:
json
{
"email":"123@qq.com"
}返回:
json
{
"success":true,
"message":"发送成功"
}校验验证码:
POST /email/verify参数:
json
{
"email":"123@qq.com",
"code":"123456"
}返回:
json
{
"success":true
}生产环境建议
实际项目中,建议进一步完善以下能力:
- 发送频率限制:限制同一邮箱短时间内重复发送验证码,例如 60 秒内只能发送一次。
- 验证码有效期:建议控制在 5 分钟以内,过期自动失效。
- 验证码使用次数:校验成功后立即删除,避免重复使用。
- 验证码复杂度:建议使用 6 位随机数字或数字+字母组合。
- 异常处理:邮件发送失败时记录日志,并做好异常提示。
- 异步发送:可结合消息队列或异步线程发送邮件,避免接口阻塞。
总结
本文采用的是生产环境推荐方案,其核心思路如下:
- 使用 QQ 邮箱 SMTP 发送验证码邮件;
- 使用 Freemarker 渲染 HTML 邮件模板;
- 使用 Redis 缓存验证码并设置过期时间;
- 服务端负责验证码校验,验证码不会返回给前端;
- 校验成功后立即删除缓存,保证验证码一次性使用。
相比直接将验证码返回给前端,这种方案更加安全,也更符合实际项目开发规范。