在本教程中,我们将继续 Spring Security 系列中的注册流程,在用户激活帐户之前重发验证链接给用户。
首先,当用户 请求另一个验证链接 时,我们需要防上一个验证链接发生过期。
我们将用新的 expireDate 重置现有的令牌,之后向用户发送一封新邮件,并附上新的链接和令牌:
@RequestMapping(value = "/user/resendRegistrationToken", method = RequestMethod.GET) @ResponseBody 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; }
我们还需要修改现有的注册功能 — 在模型上添加一些涉及到令牌到期的新逻辑:
@RequestMapping(value = "/regitrationConfirm", method = RequestMethod.GET) 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(); }
以前的功能是在某些情况下抛出异常,这些异常需要处理,我们将使用自定义的异常处理程序来处理这些异常:
@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; } }
现在我们修改 badUser.html
,使用户只有在令牌过期时才能获得新的 VerificationToken
:
<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 的响应,并根据它重定向用户。
在本文中,我们允许用户重新请求一个新的验证链接来激活账户,以防旧账户过期。
本教程的完整实现可以在 github 项目中找到 — 这是一个基于 Eclipse 的项目,应该很容易导入和运行。