使用 163 邮箱实现 Spring Boot 邮箱验证码登录
本文将详细介绍如何使用网易 163 邮箱作为 SMTP 邮件服务器,实现 Spring Boot 项目中的邮件验证码发送功能,并解决常见配置报错问题。
一、为什么需要邮箱授权码?
出于安全考虑,大多数邮箱服务商(如 163、QQ)都不允许直接使用登录密码进行第三方邮件发送。
这时候你需要在邮箱设置中开启 SMTP服务 并生成 授权码 来代替密码使用。
二、163 邮箱如何开启第三方登录(获取授权码)
⚡ 步骤如下:
- 登录网页版 163 邮箱 → 点击【设置】
- 进入左侧【POP3/SMTP/IMAP】菜单
- 勾选【开启客户端授权密码功能】
- 在【授权密码管理】中点击【新建授权码】
- 填入备注(如:“SpringBoot邮件服务”)后生成授权码
- 备份授权码,用于项目配置
三、Spring Boot 项目中如何配置 163 邮箱发送邮件
✅ Maven 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
✅ application.properties 配置
spring.mail.host=smtp.163.com
spring.mail.port=465
spring.mail.username=你的邮箱@163.com
spring.mail.password=你的授权码
spring.mail.protocol=smtps
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.ssl.enable=true
spring.mail.default-encoding=UTF-8
四、邮件发送核心代码
✅ 工具类 EmailSender.java
@Component
public class EmailSender {
@Autowired
private JavaMailSender mailSender;
@Value("${spring.mail.username}")
private String fromEmail;
public boolean send(String to, String code) {
try {
SimpleMailMessage message = new SimpleMailMessage();
message.setFrom(fromEmail);
message.setTo(to);
message.setSubject("【Boounion 登录验证码】");
message.setText("您的验证码是:" + code + ",5分钟内有效,请勿泄露!");
mailSender.send(message);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
}
五、常见错误及解决办法
错误信息 | 原因 | 解决方案 |
---|---|---|
Got bad greeting from SMTP host... EOF | 未启用 SSL 或端口错误 | 使用 port=465 +配置 ssl.enable=true |
Authentication failed | 使用了登录密码 | 用授权码替换 |
Connection timed out | 网络被墙 | 打开 465 端口 / 更换网络 |
Cannot send message | from 地址不一致 | setFrom() = spring.mail.username |
六、邮件验证码防刷优化
使用 Redis 实现“60 秒内同邮箱不能重复发送验证码”的控制,避免恶意刷接口。
✅ UserService 代码示例:
public boolean isInCooldown(String email) {
String key = "email_code_cooldown:" + email;
return Boolean.TRUE.equals(redisTemplate.hasKey(key));
}
public void markCooldown(String email) {
redisTemplate.opsForValue().set("email_code_cooldown:" + email, "1", 60, TimeUnit.SECONDS);
}
✅ Controller 中判断逻辑:
if (userService.isInCooldown(email)) {
return ResponseEntity.status(429).body("验证码已发送,请稍后再试");
}
七、邮件效果展示
验证码邮件示例:
您的验证码是:211337,5分钟内有效,请勿泄露!
八、总结
步骤 | 状态 |
---|---|
163 邮箱开通 SMTP | ✅ |
正确生成并使用授权码 | ✅ |
Spring Boot 邮件配置无误 | ✅ |
Redis 冷却控制防刷 | ✅ |
邮件内容格式清晰 | ✅ |
以上配置完成后,你就可以轻松实现一套完整、安全、可控的邮箱登录流程!
附录:完整文件(可自行补全代码)
Spring Boot 项目目录结构参考
src/main/java/org/example/
├── controller/
│ └── LoginController.java # 登录与验证码相关接口
├── model/
│ └── User.java # 用户模型类
├── service/
│ └── UserService.java # 登录逻辑与验证码缓存管理
├── util/
│ └── EmailSender.java # 邮件发送工具类
└── Main.java # SpringBoot 启动类
src/main/resources/
├── static/index.html # 前端测试页面
└── application.properties # 邮件 + Redis + DB 配置项
pom.xml ✅
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>BoounionERP</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<!-- Spring Boot 父项目 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.3</version>
<relativePath/>
</parent>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- Spring Boot Web 模块(包含内嵌 Tomcat) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot mail 模块 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<!-- Spring Boot Redis 模块 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Spring Boot 开发工具模块 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<!-- SQL Server JDBC 驱动 -->
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc</artifactId>
<version>11.2.3.jre11</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.properties ✅
spring.datasource.url=jdbc:sqlserver://localhost:1433;databaseName=BoounionDB;encrypt=true;trustServerCertificate=true
spring.datasource.username=sa
spring.datasource.password=bl123456
spring.datasource.driver-class-name=com.microsoft.sqlserver.jdbc.SQLServerDriver
spring.mail.host=smtp.163.com
spring.mail.port=465
spring.mail.username=你的邮箱
spring.mail.password=你的授权
spring.mail.protocol=smtps
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.ssl.enable=true
spring.mail.default-encoding=UTF-8
spring.data.redis.host=localhost
spring.data.redis.port=6379
spring.jpa.hibernate.ddl-auto=none
server.port=8080
Main.java ✅
package org.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* ==================================================
* This class Main is responsible for [功能描述].
*
* @author darker
* @version 1.0
* ==================================================
*/
@SpringBootApplication
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
}
LoginController.java ✅
package org.example.controller;
import org.example.model.User;
import org.example.service.UserService;
import org.example.util.EmailSender;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
/**
* ==================================================
* This class LoginController is responsible for [功能描述].
*
* @author darker
* @version 1.0
* ==================================================
*/
@RestController
@RequestMapping("/api")
public class LoginController {
private final UserService userService;
private final EmailSender emailSender;
public LoginController(UserService userService, EmailSender emailSender) {
this.userService = userService;
this.emailSender = emailSender;
}
@PostMapping("/login")
public ResponseEntity<Map<String, Object>> login(@RequestBody User user) {
String result = userService.login(user);
Map<String, Object> response = new HashMap<>();
switch (result) {
case "登录成功":
// 拼接带参数的跳转地址
String url = "https://www.baidu.com/s?wd=csdn";
response.put("status", "success");
response.put("redirectUrl", url);
return ResponseEntity.ok(response);
case "密码错误":
response.put("status", "error");
response.put("message", "密码错误");
return ResponseEntity.status(401).body(response);
case "用户不存在":
response.put("status", "error");
response.put("message", "用户不存在");
return ResponseEntity.status(404).body(response);
default:
response.put("status", "error");
response.put("message", "服务器异常");
return ResponseEntity.status(500).body(response);
}
}
@PostMapping("/login/code")
public ResponseEntity<?> sendEmailCode(@RequestBody Map<String, String> payload) {
String email = payload.get("email");
if (email == null || email.isEmpty()) {
return ResponseEntity.badRequest().body("邮箱不能为空");
}
if (!userService.isEmailRegistered(email)) {
return ResponseEntity.status(404).body("该邮箱未注册");
}
// 检查是否在冷却期(例如60秒)
if (userService.isInCooldown(email)) {
return ResponseEntity.status(429).body("验证码已发送,请稍后再试");
}
// 生成6位验证码
String code = String.format("%06d", new Random().nextInt(999999));
boolean success = emailSender.send(email, code);
if (success) {
userService.saveEmailCode(email, code);
userService.markCooldown(email);
return ResponseEntity.ok("验证码发送成功");
} else {
return ResponseEntity.status(500).body("验证码发送失败");
}
}
@PostMapping("/login/email")
public ResponseEntity<?> loginWithEmailCode(@RequestBody Map<String, String> payload) {
String email = payload.get("email");
String code = payload.get("code");
if (email == null || code == null) {
return ResponseEntity.badRequest().body("邮箱或验证码不能为空");
}
if (!userService.isEmailRegistered(email)) {
return ResponseEntity.status(404).body("该邮箱未注册");
}
if (!userService.verifyEmailCode(email, code)) {
return ResponseEntity.status(401).body("验证码错误或已过期");
}
Map<String, Object> response = new HashMap<>();
response.put("status", "success");
response.put("message", "登录成功");
return ResponseEntity.ok(response);
}
}
User.java ✅
package org.example.model;
/**
* ==================================================
* This class User is responsible for [功能描述].
*
* @author darker
* @version 1.0
* ==================================================
*/
public class User {
private String username;
private String password;
private String email;
public User() {}
public User(String username, String password) {
this.username = username;
this.password = password;
}
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
}
UserService.java ✅
package org.example.service;
import org.example.model.User;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import java.util.concurrent.TimeUnit;
import java.sql.*;
/**
* ==================================================
* This class UserService is responsible for [功能描述].
*
* @author darker
* @version 1.0
* ==================================================
*/
@Service
public class UserService {
@Value("${spring.datasource.url}")
private String dbUrl;
@Value("${spring.datasource.username}")
private String dbUser;
@Value("${spring.datasource.password}")
private String dbPassword;
private static final long CODE_COOLDOWN_SECONDS = 60;
private final StringRedisTemplate redisTemplate;
public UserService(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
// 保存验证码到 Redis,5 分钟有效
public void saveEmailCode(String email, String code) {
String key = "email_code:" + email;
redisTemplate.opsForValue().set(key, code, 5, TimeUnit.MINUTES);
}
// 验证验证码是否正确
public boolean verifyEmailCode(String email, String code) {
String key = "email_code:" + email;
String cachedCode = redisTemplate.opsForValue().get(key);
return code.equals(cachedCode);
}
// 判断邮箱是否注册
public boolean isEmailRegistered(String email) {
try (Connection conn = DriverManager.getConnection(dbUrl, dbUser, dbPassword)) {
String sql = "SELECT COUNT(*) FROM Users WHERE email = ?";
try (PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setString(1, email);
try (ResultSet rs = stmt.executeQuery()) {
rs.next();
return rs.getInt(1) > 0;
}
}
} catch (SQLException e) {
e.printStackTrace();
return false;
}
}
public boolean isInCooldown(String email) {
String key = "email_code_cooldown:" + email;
return redisTemplate.hasKey(key);
}
public void markCooldown(String email) {
String key = "email_code_cooldown:" + email;
redisTemplate.opsForValue().set(key, "1", CODE_COOLDOWN_SECONDS, TimeUnit.SECONDS);
}
public String login(User user) {
try (Connection conn = DriverManager.getConnection(dbUrl, dbUser, dbPassword)) {
String checkUserSql = "SELECT password FROM Users WHERE username = ?";
try (PreparedStatement stmt = conn.prepareStatement(checkUserSql)) {
stmt.setString(1, user.getUsername());
try (ResultSet rs = stmt.executeQuery()) {
if (!rs.next()) {
return "用户不存在";
}
String dbPassword = rs.getString("password");
if (!dbPassword.equals(user.getPassword())) {
return "密码错误";
}
return "登录成功";
}
}
} catch (SQLException e) {
e.printStackTrace();
return "数据库错误";
}
}
}
EmailSender.java ✅
package org.example.util;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Value;
/**
* ==================================================
* This class EmailSender is responsible for [功能描述].
*
* @author draker
* @version 1.0
* ==================================================
*/
@Component
public class EmailSender {
private final JavaMailSender mailSender;
@Value("${spring.mail.username}")
private String fromEmail;
public EmailSender(JavaMailSender mailSender) {
this.mailSender = mailSender;
}
/**
* 发送验证码邮件
* @param to 收件人邮箱
* @param code 验证码内容
* @return true=发送成功,false=失败
*/
public boolean send(String to, String code) {
try {
SimpleMailMessage message = new SimpleMailMessage();
message.setFrom(fromEmail);
message.setTo(to);
message.setSubject("【Boounion 登录验证码】");
message.setText("您的验证码是:" + code + ",5分钟内有效,请勿泄露!");
mailSender.send(message);
return true;
} catch (Exception e) {
System.err.println("邮件发送失败: " + e.getMessage());
return false;
}
}
}