知識庫 / Spring / Spring Security RSS 訂閱

Spring Security 註冊 – 發送驗證郵件

Spring Security
HongKong
5
02:58 PM · Dec 06 ,2025
 This article is part of a series:
• Spring Security 註冊系列
• 使用 Spring Security 的註冊流程
• 註冊 – 通過電子郵件激活新帳户
• Spring Security 註冊 – 發送驗證郵件(當前文章)
• 使用 Spring Security 進行註冊 – 密碼編碼
• 註冊 API 變為 RESTful
• Spring Security – 重置您的密碼
• 註冊 – 密碼強度和規則
• 更新您的密碼
• 通知用户從新設備或位置登錄

1. 概述

在本教程中,我們將繼續 Spring Security 的 註冊教程系列,重點介紹在用户在激活其賬户之前,驗證鏈接過期後重新發送驗證鏈接給用户的相關內容。

2. 重新發送驗證鏈接

首先,我們來看看當用户請求另一個驗證鏈接時會發生什麼,以防之前的鏈接已過期。

首先,我們將重置現有的令牌,並設置一個新的expireDate。然後,我們將向用户發送包含新鏈接/令牌的新郵件:

@GetMapping("/user/resendRegistrationToken")
public GenericResponse resendRegistrationToken(
  HttpServletRequest request, @RequestParam("token") String existingToken) {
    VerificationToken newToken = userService.generateNewVerificationToken(existingToken);
    
    User user = userService.getUser(newToken.getToken());
    String appUrl = 
      "http://" + request.getServerName() + 
      ":" + request.getServerPort() + 
      request.getContextPath();
    SimpleMailMessage email = 
      constructResendVerificationTokenEmail(appUrl, request.getLocale(), newToken, user);
    mailSender.send(email);

    return new GenericResponse(
      messages.getMessage("message.resendToken", null, request.getLocale()));
}

並且用於構建用户最終收到的電子郵件消息的實用工具——constructResendVerificationTokenEmail()

private SimpleMailMessage constructResendVerificationTokenEmail
  (String contextPath, Locale locale, VerificationToken newToken, User user) {
    String confirmationUrl = 
      contextPath + "/regitrationConfirm.html?token=" + newToken.getToken();
    String message = messages.getMessage("message.resendToken", null, locale);
    SimpleMailMessage email = new SimpleMailMessage();
    email.setSubject("Resend Registration Token");
    email.setText(message + " rn" + confirmationUrl);
    email.setFrom(env.getProperty("support.email"));
    email.setTo(user.getEmail());
    return email;
}

我們還需要修改現有的註冊功能,通過在模型中添加關於令牌到期的一些新信息:<strong about the expiration of the token

@GetMapping("/registrationConfirm")
public String confirmRegistration(
  Locale locale, Model model, @RequestParam("token") String token) {
    VerificationToken verificationToken = userService.getVerificationToken(token);
    if (verificationToken == null) {
        String message = messages.getMessage("auth.message.invalidToken", null, locale);
        model.addAttribute("message", message);
        return "redirect:/badUser.html?lang=" + locale.getLanguage();
    }

    User user = verificationToken.getUser();
    Calendar cal = Calendar.getInstance();
    if ((verificationToken.getExpiryDate().getTime() - cal.getTime().getTime()) <= 0) {
        model.addAttribute("message", messages.getMessage("auth.message.expired", null, locale));
        model.addAttribute("expired", true);
        model.addAttribute("token", token);
        return "redirect:/badUser.html?lang=" + locale.getLanguage();
    }

    user.setEnabled(true);
    userService.saveRegisteredUser(user);
    model.addAttribute("message", messages.getMessage("message.accountVerified", null, locale));
    return "redirect:/login.html?lang=" + locale.getLanguage();
}

3. 異常處理器

前面提到的功能在特定條件下——拋出異常時,這些異常需要被處理,我們將會使用 自定義異常處理器 來完成這些處理。

@ControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {

    @Autowired
    private MessageSource messages;

    @ExceptionHandler({ UserNotFoundException.class })
    public ResponseEntity<Object> handleUserNotFound(RuntimeException ex, WebRequest request) {
        logger.error("404 Status Code", ex);
        GenericResponse bodyOfResponse = new GenericResponse(
          messages.getMessage("message.userNotFound", null, request.getLocale()), "UserNotFound");
        
        return handleExceptionInternal(
          ex, bodyOfResponse, new HttpHeaders(), HttpStatus.NOT_FOUND, request);
    }

    @ExceptionHandler({ MailAuthenticationException.class })
    public ResponseEntity<Object> handleMail(RuntimeException ex, WebRequest request) {
        logger.error("500 Status Code", ex);
        GenericResponse bodyOfResponse = new GenericResponse(
          messages.getMessage(
            "message.email.config.error", null, request.getLocale()), "MailError");
        
        return handleExceptionInternal(
          ex, bodyOfResponse, new HttpHeaders(), HttpStatus.NOT_FOUND, request);
    }

    @ExceptionHandler({ Exception.class })
    public ResponseEntity<Object> handleInternal(RuntimeException ex, WebRequest request) {
        logger.error("500 Status Code", ex);
        GenericResponse bodyOfResponse = new GenericResponse(
          messages.getMessage(
            "message.error", null, request.getLocale()), "InternalError");
        
        return handleExceptionInternal(
          ex, bodyOfResponse, new HttpHeaders(), HttpStatus.NOT_FOUND, request);
    }
}

請注意:

  • 我們使用了 @ControllerAdvice 註解來處理整個應用程序中的異常
  • 我們使用了一個簡單的對象 GenericResponse 來發送響應
public class GenericResponse {
    private String message;
    private String error;

    public GenericResponse(String message) {
        super();
        this.message = message;
    }

    public GenericResponse(String message, String error) {
        super();
        this.message = message;
        this.error = error;
    }
}

4. 修改 badUser.html

我們將通過啓用用户僅在他們的令牌過期時才能獲取新的 VerificationToken 來修改 badUser.html

<html>
<head>
<title th:text="#{label.badUser.title}">bad user</title>
</head>
<body>
<h1 th:text="${param.message[0]}">error</h1>
<br>
<a th:href="@{/user/registration}" th:text="#{label.form.loginSignUp}">
  signup</a>

<div th:if="${param.expired[0]}">
<h1 th:text="#{label.form.resendRegistrationToken}">resend</h1>
<button onclick="resendToken()" 
  th:text="#{label.form.resendRegistrationToken}">resend</button>
 
<script src="jquery.min.js"></script>
<script type="text/javascript">

var serverContext = [[@{/}]];

function resendToken(){
    $.get(serverContext + "user/resendRegistrationToken?token=" + token, 
      function(data){
            window.location.href = 
              serverContext +"login.html?message=" + data.message;
    })
    .fail(function(data) {
        if(data.responseJSON.error.indexOf("MailError") > -1) {
            window.location.href = serverContext + "emailError.html";
        }
        else {
            window.location.href = 
              serverContext + "login.html?message=" + data.responseJSON.message;
        }
    });
}
</script>
</div>
</body>
</html>

請注意,我們在此使用了非常基礎的 JavaScript 和 jQuery 來處理 "/user/resendRegistrationToken" 響應,並根據響應結果重定向用户。

5. 結論

在本文中,我們允許用户請求一個新的驗證鏈接以激活其賬户,以防舊鏈接過期。

下一頁
使用 Spring Security 進行密碼編碼
之前的頁面 激活新賬户通過電子郵件
user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.