首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java Web 郵箱激活與找回密碼完整實現流程(Spring Boot + JavaMail)

     編輯:Java 相關知識

前言

用戶註冊郵箱驗證、忘記密碼重置是 Web 應用標配。本文基於 Spring Boot 2/3 + JavaMailSender,提供從郵件發送、Token 生成、鏈接構建、安全校驗到頁面跳轉的完整端到端實現方案。

一、核心依賴與配置

1. Maven 依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
</dependency>
<!-- Redis 做 Token 存儲(推薦) -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2. application.yml 郵件配置

spring:
  mail:
    host: smtp.exmail.qq.com
    port: 465
    username: noreply@yourdomain.com
    password: your_smtp_password
    protocol: smtps
    properties:
      mail:
        smtp:
          ssl:
            enable: true
          auth: true
          timeout: 10000
  redis:
    host: localhost
    port: 6379

二、通用郵件發送服務

@Service
@RequiredArgsConstructor
public class MailService {
    private final JavaMailSender mailSender;
    private final TemplateEngine templateEngine; // Thymeleaf

    public void sendHtmlMail(String to, String subject, String templateName, Context context) {
        MimeMessage message = mailSender.createMimeMessage();
        try {
            MimeMessageHelper helper = new MimeMessageHelper(message, true, StandardCharsets.UTF_8.name());
            helper.setTo(to);
            helper.setSubject(subject);
            String html = templateEngine.process(templateName, context);
            helper.setText(html, true);
            mailSender.send(message);
        } catch (MessagingException e) {
            throw new MailSendException("郵件發送失敗: " + e.getMessage(), e);
        }
    }
}

三、郵箱激活流程

1. 註冊時發送激活郵件

@PostMapping("/register")
public Result register(@Valid @RequestBody RegisterDTO dto) {
    // 1. 保存用戶,status=INACTIVE
    User user = userService.saveInactive(dto);
    
    // 2. 生成激活 Token(JWT 或隨機字符串 + Redis)
    String token = UUID.randomUUID().toString().replace("-", "");
    redisTemplate.opsForValue().set("email:activate:" + token, user.getId(), 24, TimeUnit.HOURS);
    
    // 3. 構建激活鏈接
    String activateUrl = String.format("%s/activate?token=%s", frontendBaseUrl, token);
    
    // 4. 發送郵件
    Context ctx = new Context();
    ctx.setVariable("username", user.getUsername());
    ctx.setVariable("activateUrl", activateUrl);
    ctx.setVariable("expireHours", 24);
    mailService.sendHtmlMail(user.getEmail(), "激活您的賬號", "mail/activate", ctx);
    
    return Result.success("註冊成功,請查收郵件激活");
}

2. 激活頁面控制器

@GetMapping("/activate")
public String activate(@RequestParam String token, Model model) {
    String userIdStr = redisTemplate.opsForValue().get("email:activate:" + token);
    if (userIdStr == null) {
        model.addAttribute("msg", "激活鏈接無效或已過期");
        return "activate_fail";
    }
    
    Long userId = Long.parseLong(userIdStr);
    User user = userService.getById(userId);
    if (user == null || user.getStatus() == UserStatus.ACTIVE) {
        model.addAttribute("msg", "用戶不存在或已激活");
        return "activate_fail";
    }
    
    userService.activate(userId);
    redisTemplate.delete("email:activate:" + token); // 一次性 Token
    model.addAttribute("msg", "激活成功,請登錄");
    return "activate_success";
}

3. Thymeleaf 郵件模板 resources/templates/mail/activate.html

<!DOCTYPE html>
<html xmlns:th="https://www.thymeleaf.org">
<body style="font-family: 'Microsoft YaHei', sans-serif;">
    <div style="max-width: 600px; margin: 0 auto; padding: 20px; border: 1px solid #e0e0e0; border-radius: 8px;">
        <h2 style="color: #333;">歡迎註冊,<span th:text="${username}">用戶</span></h2>
        <p>請點擊下方按鈕激活您的郵箱(鏈接有效期 <span th:text="${expireHours}">24</span> 小時):</p>
        <div style="text-align: center; margin: 30px 0;">
            <a th:href="${activateUrl}" 
               style="background: #1890ff; color: white; padding: 12px 24px; border-radius: 4px; text-decoration: none; display: inline-block;">
                立即激活
            </a>
        </div>
        <p style="color: #999; font-size: 12px;">若按鈕無法點擊,請複製鏈接至瀏覽器:<br/><span th:text="${activateUrl}"></span></p>
    </div>
</body>
</html>

四、忘記密碼/重置密碼流程

1. 發送重置驗證碼

@PostMapping("/forgot-password")
public Result forgotPassword(@RequestParam String email) {
    User user = userService.findByEmail(email);
    if (user == null) {
        // 安全:不提示用戶是否存在,統一返回成功
        return Result.success("若郵箱存在,重置鏈接已發送");
    }
    
    // 生成 6 位數字驗證碼,存 Redis 15 分鐘
    String code = String.format("%06d", new Random().nextInt(1_000_000));
    redisTemplate.opsForValue().set("pwd:reset:" + email, code, 15, TimeUnit.MINUTES);
    
    Context ctx = new Context();
    ctx.setVariable("code", code);
    ctx.setVariable("username", user.getUsername());
    ctx.setVariable("expireMinutes", 15);
    mailService.sendHtmlMail(email, "密碼重置驗證碼", "mail/reset_code", ctx);
    
    return Result.success("若郵箱存在,重置鏈接已發送");
}

2. 驗證碼校驗 + 設置新密碼

@PostMapping("/reset-password")
public Result resetPassword(@Valid @RequestBody ResetPasswordDTO dto) {
    // 1. 校驗驗證碼
    String cachedCode = redisTemplate.opsForValue().get("pwd:reset:" + dto.getEmail());
    if (cachedCode == null || !cachedCode.equals(dto.getCode())) {
        return Result.error("驗證碼錯誤或已過期");
    }
    
    // 2. 更新密碼(BCrypt 加密)
    userService.updatePassword(dto.getEmail(), passwordEncoder.encode(dto.getNewPassword()));
    
    // 3. 刪除驗證碼,強制登出(刪除所有 Refresh Token)
    redisTemplate.delete("pwd:reset:" + dto.getEmail());
    tokenService.revokeAllUserTokens(dto.getEmail());
    
    return Result.success("密碼重置成功,請重新登錄");
}

五、安全加固清單

風險點防範措施
Token 洩露使用 UUID + Redis 存儲,不直接在鏈接攜帶 userId;設置過期時間(激活 24h,重置 15min)
枚舉用戶郵箱忘記密碼接口統一返回成功提示,不區分郵箱是否存在
暴力破解驗證碼限流:同 IP/郵箱 1 分鐘 1 次,1 小時 5 次;驗證碼 6 位數字 + 15 分鐘過期
重置後會話劫持重置成功後撤銷所有 Refresh Token,強制重新登錄
郵件內容注入Thymeleaf 自動轉義,禁止在模板中直接輸出未轉義變量 th:utext

六、前端頁面關鍵點

  • 激活頁:純 GET 請求,無表單,直接展示結果,引導去登錄
  • 忘記密碼頁:輸入郵箱 → 發送驗證碼 → 輸入驗證碼 + 新密碼 + 確認密碼 → 提交
  • 倒計時按鈕:disabled 狀態防止重複發送
  • 密碼強度提示:前端正則預校驗,後端再次校驗

結語

郵箱激活與密碼重置核心在於「一次性 Token + 時效性 + 安全校驗」。Spring Boot + JavaMail + Redis + Thymeleaf 組合開箱即用,關鍵細節:統一錯誤提示防枚舉、重置後撤銷 Token、限流防暴力、模板引擎防注入。按此框架擴展即可支援手機短信、多語言、單點登錄等高級場景。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved