Bài viết gốc xem tại đây.

1. Tổng quan

Trong hướng dẫn này, chúng ta tiếp tục với chuỗi bài viết Đăng ký với Spring Security về một vấn đề là gửi lại đường dẫn xác thực cho người dùng trong trường hợp nó hết hạn trước khi họ kích hoạt tài khoản của mình.

2. Gửi lại đường dẫn xác thực tài khoản

Đầu tiên, chúng ta hãy xem xét điều gì sẽ xảy ra khi người dùng yêu cầu một đường dẫn xác minh khác trong trường hợp liên kết trước đó đã hết hạn.

Bước 1 - chúng ta sẽ reset lại token đã tồn tại với expireDate mới. Sau đó, chúng ta sẽ gửi lại cho người dùng một email mới với đường dẫn/token:

@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()));
}

Và utility cho việc xây dựng tin nhắn email cho người dùng - 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;
}

Chúng ta cần thay đổi chức năng đăng ký hiện tại - thêm vài thông tin trong model về hạn sử dụng của 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. Xử lý Exception

Ở chức năng trước đó, trong điều kiện nhất định - sẽ ném ra exception; những exception này cần được xử lý, và chúng ta sẽ làm điều đó với một exception handler tuỳ chỉnh:

@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);
    }
}

Chú ý:

  • Chúng ta đã sử dụng annotation @ControllerAdvice để xử lý các exception trong toàn bộ ứng dụng
  • Chúng ta đã sử dụng một đối tượng đơn giản GenericResponse để gửi phản hồi:
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. Thay đổi badUser.html

Chúng ta sẽ thay đổi badUser.html hiện tại là nếu token của họ bị hết hạn, họ mới có thể nhận được một VerificationToken mới.

<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>

Chú ý rằng, chúng ta sẽ sử dụng vài thứ cơ bản của javascript và JQuery ở đây để phản hồi “/user/resendRegistrationToken” và điều hướng người dùng dựa vào điều này.

5. Tổng kết

Đây là bài viết chúng ta cho phép người dùng yêu cầu một đường dẫn xác thực mới để kích hoạt tài khoản của họ trong trường hợp cái cũ đã hết hạn.

Các đoạn code trên có thể xem ở GitHub này - đây là project ở Eclipse vì vậy nó rất dễ dàng import và chạy nó.