AI摘要
本文详细介绍了如何在Spring Boot应用中实现邮箱验证码注册和登录功能。包括功能概述、技术栈、实现步骤、邮件服务配置、创建邮件服务、验证码生成和存储、控制器实现、服务层实现、邮件模板、前端实现以及最佳实践。通过这些步骤,可以为用户提供安全、便捷的注册和登录方式。
在现代Web应用中,邮箱验证码注册和登录是非常常见的功能,不仅增强了账号安全性,也简化了用户的注册和登录流程。本文将详细介绍如何在Spring Boot应用中实现邮箱验证码注册和登录功能。
1. 功能概述
我们将实现以下功能:
- 邮箱+验证码注册
- 邮箱+验证码登录
- 邮箱+密码登录(传统登录方式)
这些功能涵盖了用户注册和登录的主要场景,为用户提供了灵活的选择。
2. 技术栈
- Spring Boot 3.2.0
- Spring Security
- Spring Mail
- Thymeleaf (邮件模板)
- MyBatis Flex
- JWT (认证)
3. 实现步骤
3.1 邮件服务配置
首先,我们需要配置邮件服务。在application.properties
文件中添加:
# 邮件服务配置
spring.mail.host=smtp.qq.com
spring.mail.port=587
spring.mail.username=your-email@qq.com
spring.mail.password=your-authorization-code
spring.mail.default-encoding=UTF-8
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
对于QQ邮箱,密码不是邮箱密码,而是授权码,需要在QQ邮箱设置中获取。
3.2 创建邮件服务
创建EmailService
接口和实现类:
public interface EmailService {
boolean sendVerificationEmail(String to, String subject, String code, String type);
}
@Service
public class EmailServiceImpl implements EmailService {
private static final Logger logger = LoggerFactory.getLogger(EmailServiceImpl.class);
private final JavaMailSender mailSender;
private final TemplateEngine templateEngine;
@Value("${spring.mail.username}")
private String from;
public EmailServiceImpl(JavaMailSender mailSender, TemplateEngine templateEngine) {
this.mailSender = mailSender;
this.templateEngine = templateEngine;
}
@Override
public boolean sendVerificationEmail(String to, String subject, String code, String type) {
try {
// 准备Thymeleaf模板上下文
Context context = new Context();
context.setVariable("code", code);
context.setVariable("type", type);
context.setVariable("time", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
// 处理模板生成HTML内容
String htmlContent = templateEngine.process("email/verification-code", context);
// 创建MIME消息
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
helper.setFrom(from);
helper.setTo(to);
helper.setSubject(subject);
helper.setText(htmlContent, true);
// 发送邮件
mailSender.send(message);
logger.info("邮件发送成功:{}", to);
return true;
} catch (Exception e) {
logger.error("邮件发送失败", e);
return false;
}
}
}
3.3 验证码生成和存储
为了管理验证码,我们需要创建生成验证码的方法和存储验证码的机制:
// 在UserServiceImpl中
private final Map<String, String> emailCodeCache = new HashMap<>();
private String generateRandomCode(int length) {
StringBuilder sb = new StringBuilder();
Random random = new Random();
for (int i = 0; i < length; i++) {
sb.append(random.nextInt(10));
}
return sb.toString();
}
@Override
public boolean sendEmailCode(String email, String type) {
try {
// 生成6位随机验证码
String code = generateRandomCode(6);
// 将验证码存入缓存
emailCodeCache.put(email + ":" + type, code);
// 验证码类型对应的邮件主题
String subject;
switch (type) {
case "login":
subject = "登录验证码";
break;
case "register":
subject = "注册验证码";
break;
case "reset":
subject = "重置密码验证码";
break;
default:
subject = "验证码";
}
// 使用邮件服务发送验证码
return emailService.sendVerificationEmail(email, subject, code, type);
} catch (Exception e) {
logger.error("发送邮箱验证码失败", e);
return false;
}
}
@Override
public String getEmailCode(String email, String type) {
return emailCodeCache.get(email + ":" + type);
}
@Override
public void removeEmailCode(String email, String type) {
emailCodeCache.remove(email + ":" + type);
}
在生产环境中,应该使用Redis等分布式缓存来存储验证码,并设置过期时间。
3.4 控制器实现
接下来,实现处理注册和登录请求的控制器方法:
发送邮箱验证码
@PostMapping("/send/email")
public ResponseEntity<?> sendEmailCode(
@RequestParam @NotBlank @Email String email,
@RequestParam @NotBlank String type) {
logger.info("发送邮箱验证码,邮箱: {}, 类型: {}", email, type);
boolean result = userService.sendEmailCode(email, type);
if (result) {
return ResponseEntity.ok().body(Map.of("code", 200, "message", "验证码发送成功"));
} else {
return ResponseEntity.badRequest().body(Map.of("code", 400, "message", "验证码发送失败"));
}
}
邮箱+验证码注册
@PostMapping("/register")
public ResponseEntity<?> register(
@RequestBody(required = false) Map<String, String> registerRequest,
@RequestParam(required = false) String email,
@RequestParam(required = false) String password,
@RequestParam(required = false) String code,
@RequestParam(required = false) String nickname) {
// 优先使用URL参数,如果没有则使用请求体
if (registerRequest != null) {
if (email == null) email = registerRequest.get("email");
if (password == null) password = registerRequest.get("password");
if (code == null) code = registerRequest.get("code");
if (nickname == null) nickname = registerRequest.get("nickname");
}
if (email == null || password == null || code == null) {
return ResponseEntity.badRequest().body(Map.of("code", 400, "message", "邮箱、密码和验证码不能为空"));
}
logger.info("用户一步式注册,邮箱: {}", email);
// 验证验证码
String cachedCode = userService.getEmailCode(email, "register");
if (cachedCode == null || !cachedCode.equals(code)) {
return ResponseEntity.status(400).body(Map.of("code", 400, "message", "验证码错误或已过期"));
}
// 创建用户
User user = new User();
user.setEmail(email);
user.setUsername(email); // 使用邮箱作为用户名
user.setPassword(password); // 未加密的密码,在service层会加密
user.setNickname(nickname != null ? nickname : "用户" + email.substring(0, email.indexOf("@")));
// 调用服务层方法创建用户
Map<String, Object> registerResult = userService.register(user, true);
int registerCode = (int) registerResult.get("code");
// 验证码验证成功后,删除缓存中的验证码
if (registerCode == 200) {
userService.removeEmailCode(email, "register");
}
return ResponseEntity.status(registerCode == 200 ? 200 : 400).body(registerResult);
}
邮箱+验证码登录
@PostMapping("/login/email")
public ResponseEntity<?> loginByEmail(
@RequestParam @NotBlank @Email String email,
@RequestParam @NotBlank String code) {
logger.info("邮箱验证码登录,邮箱: {}", email);
Map<String, Object> result = userService.emailLogin(email, code);
int code2 = (int) result.get("code");
return ResponseEntity.status(code2 == 200 ? 200 : 400).body(result);
}
3.5 服务层实现
在服务层,实现用户注册和登录的业务逻辑:
邮箱验证码登录
@Override
public Map<String, Object> emailLogin(String email, String code) {
Map<String, Object> result = new HashMap<>();
// 验证验证码
String cachedCode = getEmailCode(email, "login");
if (cachedCode == null || !cachedCode.equals(code)) {
result.put("code", 400);
result.put("message", "验证码错误或已过期");
return result;
}
// 清除验证码缓存
removeEmailCode(email, "login");
// 根据邮箱查询用户
User user = getOneOrNull(QueryWrapper.create().where("email = ?", email));
if (user == null) {
result.put("code", 400);
result.put("message", "用户不存在,请先注册");
return result;
}
// 检查用户状态
if (user.getStatus() != 1) {
result.put("code", 403);
result.put("message", "账号已被禁用");
return result;
}
// 更新登录信息
user.setLastLoginTime(LocalDateTime.now());
updateById(user);
// 生成token
String token = jwtUtil.generateToken(user.getUsername());
// 准备返回数据
Map<String, Object> userData = new HashMap<>();
userData.put("id", user.getId());
userData.put("username", user.getUsername());
userData.put("nickname", user.getNickname());
userData.put("avatar", user.getAvatar());
userData.put("email", user.getEmail());
result.put("code", 200);
result.put("message", "登录成功");
result.put("data", new HashMap<String, Object>() {{
put("token", token);
put("user", userData);
}});
return result;
}
3.6 邮件模板
创建verification-code.html
邮件模板(放在resources/templates/email
目录下):
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>验证码</title>
<style>
body {
font-family: 'Arial', sans-serif;
line-height: 1.6;
color: #333;
max-width: 600px;
margin: 0 auto;
padding: 20px;
}
.container {
border: 1px solid #e6e6e6;
border-radius: 5px;
padding: 20px;
background-color: #f9f9f9;
}
.header {
text-align: center;
margin-bottom: 20px;
}
.code {
text-align: center;
font-size: 24px;
font-weight: bold;
letter-spacing: 5px;
color: #ff6600;
margin: 20px 0;
padding: 10px;
background-color: #fff;
border: 1px dashed #ccc;
border-radius: 5px;
}
.footer {
margin-top: 30px;
font-size: 12px;
color: #999;
text-align: center;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h2>宠物商店 - 验证码</h2>
</div>
<p>尊敬的用户:</p>
<p>您正在进行<span th:text="${type == 'register'} ? '注册' : (${type == 'login'} ? '登录' : '重置密码')"></span>操作,您的验证码为:</p>
<div class="code" th:text="${code}">123456</div>
<p>验证码有效期为5分钟,请勿将验证码泄露给他人。</p>
<p>如非本人操作,请忽略此邮件。</p>
<div class="footer">
<p>宠物商店团队</p>
<p>发送时间:<span th:text="${time}">2023-01-01 12:00:00</span></p>
</div>
</div>
</body>
</html>
4. 前端实现
在前端,我们需要创建对应的API调用和页面:
4.1 API层
// 用户相关接口
export const userApi = {
// 邮箱验证码注册
register: (data) => {
return request({
url: '/user/register',
method: 'post',
data
})
},
// 邮箱验证码登录
emailLogin: (data) => {
return request({
url: '/user/login/email',
method: 'post',
data
})
},
// 发送邮箱验证码
sendEmailCode: (email, type) => {
return request({
url: '/user/send/email',
method: 'post',
params: {
email,
type // login-登录, register-注册, reset-重置密码
}
})
}
}
4.2 注册页面
<template>
<div class="form-container">
<div class="form-item">
<label class="label">邮箱</label>
<input type="text" class="input" placeholder="请输入邮箱" v-model="form.email" />
</div>
<div class="form-item code-group">
<label class="label">验证码</label>
<div class="code-input-wrapper">
<input type="text" class="input code-input" placeholder="请输入验证码" v-model="form.code" />
<button class="code-btn" :disabled="countdown > 0" @click="sendCode">
{{ countdown > 0 ? `${countdown}s后重发` : '获取验证码' }}
</button>
</div>
</div>
<div class="form-item">
<label class="label">密码</label>
<input type="password" class="input" placeholder="请输入密码" v-model="form.password" />
</div>
<div class="form-item">
<label class="label">昵称</label>
<input type="text" class="input" placeholder="请输入昵称" v-model="form.nickname" />
</div>
<button class="submit-btn" @click="register">注册</button>
</div>
</template>
<script>
import { userApi } from '@/api'
export default {
data() {
return {
form: {
email: '',
code: '',
password: '',
nickname: ''
},
countdown: 0
}
},
methods: {
sendCode() {
// 验证邮箱格式
if (!this.form.email || !/^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(this.form.email)) {
this.$message.error('请输入正确的邮箱地址')
return
}
userApi.sendEmailCode(this.form.email, 'register').then(res => {
if (res.code === 200) {
this.$message.success('验证码发送成功')
this.countdown = 60
const timer = setInterval(() => {
this.countdown--
if (this.countdown <= 0) {
clearInterval(timer)
}
}, 1000)
} else {
this.$message.error(res.message || '验证码发送失败')
}
})
},
register() {
// 表单验证
if (!this.form.email) {
this.$message.error('请输入邮箱')
return
}
if (!this.form.code) {
this.$message.error('请输入验证码')
return
}
if (!this.form.password) {
this.$message.error('请输入密码')
return
}
userApi.register(this.form).then(res => {
if (res.code === 200) {
this.$message.success('注册成功')
// 跳转到登录页
this.$router.push('/login')
} else {
this.$message.error(res.message || '注册失败')
}
}).catch(err => {
this.$message.error('注册失败,请稍后再试')
})
}
}
}
</script>
4.3 登录页面
<template>
<div class="login-container">
<div class="login-tabs">
<div :class="['tab-item', loginType === 'password' ? 'active' : '']" @click="loginType = 'password'">密码登录</div>
<div :class="['tab-item', loginType === 'code' ? 'active' : '']" @click="loginType = 'code'">验证码登录</div>
</div>
<!-- 密码登录 -->
<div v-if="loginType === 'password'" class="form-content">
<div class="form-item">
<label class="label">邮箱</label>
<input type="text" class="input" placeholder="请输入邮箱" v-model="passwordForm.username" />
</div>
<div class="form-item">
<label class="label">密码</label>
<input type="password" class="input" placeholder="请输入密码" v-model="passwordForm.password" />
</div>
<button class="submit-btn" @click="loginByPassword">登录</button>
</div>
<!-- 验证码登录 -->
<div v-if="loginType === 'code'" class="form-content">
<div class="form-item">
<label class="label">邮箱</label>
<input type="text" class="input" placeholder="请输入邮箱" v-model="codeForm.email" />
</div>
<div class="form-item code-group">
<label class="label">验证码</label>
<div class="code-input-wrapper">
<input type="text" class="input code-input" placeholder="请输入验证码" v-model="codeForm.code" />
<button class="code-btn" :disabled="countdown > 0" @click="sendCode">
{{ countdown > 0 ? `${countdown}s后重发` : '获取验证码' }}
</button>
</div>
</div>
<button class="submit-btn" @click="loginByCode">登录</button>
</div>
</div>
</template>
<script>
import { userApi } from '@/api'
export default {
data() {
return {
loginType: 'password',
passwordForm: {
username: '',
password: ''
},
codeForm: {
email: '',
code: ''
},
countdown: 0
}
},
methods: {
sendCode() {
if (!this.codeForm.email || !/^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(this.codeForm.email)) {
this.$message.error('请输入正确的邮箱地址')
return
}
userApi.sendEmailCode(this.codeForm.email, 'login').then(res => {
if (res.code === 200) {
this.$message.success('验证码发送成功')
this.countdown = 60
const timer = setInterval(() => {
this.countdown--
if (this.countdown <= 0) {
clearInterval(timer)
}
}, 1000)
} else {
this.$message.error(res.message || '验证码发送失败')
}
})
},
loginByPassword() {
if (!this.passwordForm.username) {
this.$message.error('请输入邮箱')
return
}
if (!this.passwordForm.password) {
this.$message.error('请输入密码')
return
}
userApi.login(this.passwordForm).then(res => {
if (res.code === 200) {
// 保存token
localStorage.setItem('token', res.data.token)
// 保存用户信息
localStorage.setItem('user', JSON.stringify(res.data.user))
this.$message.success('登录成功')
// 跳转到首页
this.$router.push('/')
} else {
this.$message.error(res.message || '登录失败')
}
}).catch(err => {
this.$message.error('登录失败,请稍后再试')
})
},
loginByCode() {
if (!this.codeForm.email) {
this.$message.error('请输入邮箱')
return
}
if (!this.codeForm.code) {
this.$message.error('请输入验证码')
return
}
userApi.emailLogin(this.codeForm).then(res => {
if (res.code === 200) {
// 保存token
localStorage.setItem('token', res.data.token)
// 保存用户信息
localStorage.setItem('user', JSON.stringify(res.data.user))
this.$message.success('登录成功')
// 跳转到首页
this.$router.push('/')
} else {
this.$message.error(res.message || '登录失败')
}
}).catch(err => {
this.$message.error('登录失败,请稍后再试')
})
}
}
}
</script>
5. 最佳实践
在实现邮箱验证码注册登录功能时,有以下最佳实践:
-
验证码安全性:
- 设置验证码过期时间(通常5-10分钟)
- 限制验证码尝试次数,防止暴力破解
- 使用Redis存储验证码,方便设置过期时间并在分布式环境中共享
-
邮件模板设计:
- 使用响应式设计,适配各种设备
- 邮件内容简洁明了,突出验证码
- 提供品牌识别元素(Logo、颜色等)
-
性能优化:
- 邮件发送应异步处理,不阻塞主线程
- 考虑使用消息队列处理邮件发送任务
- 邮件服务配置连接池,优化性能
-
安全防护:
- 防止短时间内重复发送验证码
- 验证邮箱格式
- 实现IP限制,防止恶意请求
6. 总结
邮箱验证码注册登录功能是现代Web应用的标准功能,通过本文的实现,我们可以为用户提供一种安全、便捷的注册和登录方式。
在实际应用中,可以根据具体需求扩展和优化这些功能,例如:
- 添加邮箱绑定和解绑功能
- 实现邮箱地址变更功能
- 支持通过邮箱重置密码
- 结合第三方登录(如QQ、微信等)
希望本文对你实现邮箱验证码注册登录功能有所帮助!