使用 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 {@Autowiredprivate 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.SQLServerDriverspring.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-8spring.data.redis.host=localhost
spring.data.redis.port=6379spring.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;}}
}