使用 163 郵箱實現 Spring Boot 郵箱驗證碼登錄

使用 163 郵箱實現 Spring Boot 郵箱驗證碼登錄

本文將詳細介紹如何使用網易 163 郵箱作為 SMTP 郵件服務器,實現 Spring Boot 項目中的郵件驗證碼發送功能,并解決常見配置報錯問題。


一、為什么需要郵箱授權碼?

出于安全考慮,大多數郵箱服務商(如 163、QQ)都不允許直接使用登錄密碼進行第三方郵件發送
這時候你需要在郵箱設置中開啟 SMTP服務 并生成 授權碼 來代替密碼使用。


二、163 郵箱如何開啟第三方登錄(獲取授權碼)

? 步驟如下:

  1. 登錄網頁版 163 郵箱 → 點擊【設置】
  2. 進入左側【POP3/SMTP/IMAP】菜單
  3. 勾選【開啟客戶端授權密碼功能】
  4. 在【授權密碼管理】中點擊【新建授權碼】
  5. 填入備注(如:“SpringBoot郵件服務”)后生成授權碼
  6. 備份授權碼,用于項目配置

在這里插入圖片描述
在這里插入圖片描述


三、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 messagefrom 地址不一致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;}}
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/905904.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/905904.shtml
英文地址,請注明出處:http://en.pswp.cn/news/905904.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

深入解析Spring Boot與Spring Security的集成實踐

深入解析Spring Boot與Spring Security的集成實踐 引言 在現代Web應用開發中&#xff0c;安全性是一個不可忽視的重要方面。Spring Security作為Spring生態中的安全框架&#xff0c;提供了強大的認證和授權功能。本文將結合Spring Boot&#xff0c;詳細介紹如何集成Spring Se…

C#將1GB大圖裁剪為8張圖片

C#處理超大圖片&#xff08;1GB&#xff09;需要特別注意內存管理和性能優化。以下是幾種高效裁剪方案&#xff1a; 方法1&#xff1a;使用System.Drawing分塊處理&#xff08;內存優化版&#xff09; using System; using System.Drawing; using System.Drawing.Imaging; us…

Linux系統啟動相關:vmlinux、vmlinuz、zImage,和initrd 、 initramfs,以及SystemV 和 SystemD

目錄 一、vmlinux、vmlinuz、zImage、bzImage、uImage 二、initrd 和 initramfs 1、initrd&#xff08;Initial RAM Disk&#xff09; 2、initramfs&#xff08;Initial RAM Filesystem&#xff09; 3、initrd vs. initramfs 對比 4. 如何查看和生成 initramfs 三、Syste…

AIStarter Windows 版本迎來重磅更新!模型插件工作流上線,支持 Ollama / ComfyUI 等多平臺本地部署模型統一管理

如果你正在使用 AIStarter 工具進行本地 AI 模型部署 &#xff0c;那么這條消息對你來說非常重要&#xff01; 在最新推出的 AIStarter Windows 正式版更新中 &#xff0c;官方對整個平臺進行了功能重構和性能優化&#xff0c;尤其是新增了「模型插件工作流 」功能&#xff0c…

深入理解橋接模式:解耦抽象與實現的設計藝術

一、為什么需要橋接模式&#xff1f;從“類爆炸”問題說起 你是否遇到過這樣的開發困境&#xff1f; 當需要為系統擴展新功能時&#xff0c;繼承體系像滾雪球一樣越變越臃腫&#xff1a;新增一種遙控器類型&#xff0c;需要為電視、音響各寫一個子類&#xff1b;新增一種設備類…

Java 中的泛型原理與實踐案例

引言&#xff1a;為什么需要泛型 在Java 5之前&#xff0c;集合類只能存儲Object類型的對象&#xff0c;這帶來了兩個主要問題&#xff1a; 類型不安全&#xff1a;可以向集合中添加任何類型的對象&#xff0c;容易出錯繁瑣的類型轉換&#xff1a;從集合中取出元素時需要手動…

springboot3+vue3融合項目實戰-大事件文章管理系統-獲取文章分類詳情

GetMapping("/detail")public Result<Category> detail(Integer id){Category c categoryService.findById(id);return Result.success(c);}在CategoryService接口增加 Category findById(Integer id); 在CategoryServiceImpl增加 Overridepublic Category f…

從零開始創建一個 Next.js 項目并實現一個 TodoList 示例

Next.js 是一個基于 React 的服務端渲染框架&#xff0c;它提供了很多開箱即用的功能&#xff0c;如自動路由、API 路由、靜態生成、增量靜態再生等。本文將帶你一步步創建一個 Next.js 項目&#xff0c;并實現一個簡單的 TodoList 功能。 效果地址 &#x1f9f1; 安裝 Next.j…

分布式鎖: Redisson紅鎖(RedLock)原理與實現細節

分布式鎖是分布式系統的核心基礎設施&#xff0c;但 單節點 Redis 鎖在高可用場景下存在致命缺陷&#xff1a;當 Redis 主節點宕機時&#xff0c;從節點可能因異步復制未完成而丟失鎖信息&#xff0c;導致多個客戶端同時持有鎖。為此&#xff0c;Redis 作者 Antirez 提出了 Red…

c++多態面試題之(析構函數與虛函數)

有以下問題展開 析構函數要不要定義成虛函數&#xff1f;基類的析構函數要不要定義成虛函數&#xff1f;如果不定義會有什么問題&#xff0c;定義了在什么場景下起作用。 1. 基類析構函數何時必須定義為虛函數&#xff1f; 當且僅當通過基類指針&#xff08;或引用&#xff09;…

Python高級進階:Vim與Vi使用指南

李升偉 整理 在 Python 高級進階中&#xff0c;使用 Vim 或 Vi 作為代碼編輯器可以顯著提升開發效率&#xff0c;尤其是在遠程服務器開發或快速腳本編輯時。以下是關于它們在 Python 開發中的高級應用詳解&#xff1a; 1. Vim/Vi 簡介 Vi&#xff1a;經典的 Unix 文本編輯器…

Dify中使用插件LocalAI配置模型供應商報錯

服務器使用vllm運行大模型&#xff0c;今天在Dify中使用插件LocalAI配置模型供應商后&#xff0c;使用工作流的時候&#xff0c;報錯&#xff1a;“Run failed: PluginInvokeError: {"args":{},"error_type":"ValueError","message":&…

深度學習驅動下的目標檢測技術:原理、算法與應用創新(二)

三、主流深度學習目標檢測算法剖析 3.1 R - CNN 系列算法 3.1.1 R - CNN 算法詳解 R - CNN&#xff08;Region - based Convolutional Neural Networks&#xff09;是將卷積神經網絡&#xff08;CNN&#xff09;應用于目標檢測領域的開創性算法&#xff0c;其在目標檢測發展歷…

【Umi】項目初始化配置和用戶權限

app.tsx import { RunTimeLayoutConfig } from umijs/max; import { history, RequestConfig } from umi; import { getCurrentUser } from ./services/auth; import { message } from antd;// 獲取用戶信息 export async function getInitialState(): Promise<{currentUse…

[學習] RTKLib詳解:qzslex.c、rcvraw.c與solution.c

RTKLib詳解&#xff1a;qzslex.c、rcvraw.c與solution.c 本文是 RTKLlib詳解 系列文章的一篇&#xff0c;目前該系列文章還在持續總結寫作中&#xff0c;以發表的如下&#xff0c;有興趣的可以翻閱。 [學習] RTKlib詳解&#xff1a;功能、工具與源碼結構解析 [學習]RTKLib詳解…

移植RTOS,發現任務棧溢出怎么辦?

目錄 1、硬件檢測方法 2、軟件檢測方法 3、預防堆棧溢出 4、處理堆棧溢出 在嵌入式系統中&#xff0c;RTOS通過管理多個任務來滿足嚴格的時序要求。任務堆棧管理是RTOS開發中的關鍵環節&#xff0c;尤其是在將RTOS移植到新硬件平臺時。堆棧溢出是嵌入式開發中常見的錯誤&am…

window 顯示驅動開發-使用有保證的協定 DMA 緩沖區模型

Windows Vista 的顯示驅動程序模型保證呈現設備的 DMA 緩沖區和修補程序位置列表的大小。 修補程序位置列表包含 DMA 緩沖區中命令引用的資源的物理內存地址。 在有保證的協定模式下&#xff0c;用戶模式顯示驅動程序知道 DMA 緩沖區和修補程序位置列表的確切大小&#xff0c;…

SD-HOST Controller design-----SD CLK 設計

hclk的分頻電路&#xff0c;得到的分頻時鐘作為sd卡時鐘。 該模塊最終輸出兩個時鐘&#xff1a;一個為fifo_sd_clk,另一個為out_sd_clk_dft。當不分頻時&#xff0c;fifo_sd_clk等于hclk&#xff1b;當分頻時候&#xff0c;div_counter開始計數&#xff0c;記到相應分頻的時候…

完全背包問題中「排列數」與「組合數」的核心區別

&#x1f3af; 一句話理解 求組合數&#xff08;不計順序&#xff09; → 外層遍歷物品&#xff0c;內層遍歷背包容量 求排列數&#xff08;計順序&#xff09; → 外層遍歷背包容量&#xff0c;內層遍歷物品 &#x1f3b2; 舉例說明 假設有硬幣 [1, 2, 3]&#xff0c;目標金…

NHANES指標推薦:MDS

文章題目&#xff1a;The association between magnesium depletion score (MDS) and overactive bladder (OAB) among the U.S. population DOI&#xff1a;10.1186/s41043-025-00846-x 中文標題&#xff1a;美國人群鎂耗竭評分 &#xff08;MDS&#xff09; 與膀胱過度活動癥…