I usually post about Security on Twitter - you can follow me there:
1. Overview
In this tutorial – we’re continuing the ongoing Registration with Spring Security series with a look at the basic “I forgot my password” feature – so that the user can safely reset their own password when they need to.
2. The Password Reset Token
Let’s start by creating a PasswordResetToken entity to use it for resetting the users password:
@Entity public class PasswordResetToken { private static final int EXPIRATION = 60 * 24; @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String token; @OneToOne(targetEntity = User.class, fetch = FetchType.EAGER) @JoinColumn(nullable = false, name = "user_id") private User user; private Date expiryDate; }
When a password reset is triggered – a token will be created and a special link containing this token will be emailed to the user.
The token and the link will only be valid for a set period of time (24 hours in this example).
3. forgotPassword.html
The first page in the process is the “I forgot my password” page – where the user is prompted for their email address in order for the actual reset process to start.
So – let’s craft a simple forgotPassword.html asking the user for an email address:
<!DOCTYPE html> <%@ page contentType="text/html;charset=UTF-8" language="java"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%> <%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%> <%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags"%> <%@ page session="false"%> <html> <head> <link href="<c:url value="/resources/bootstrap.css" />" rel="stylesheet"> <meta http-equiv="Content-Type" content="text/html; charset=US-ASCII"> <title><spring:message code="message.resetPassword"></spring:message></title> </head> <body> <div class="container"> <div class="span12"> <h1> <spring:message code="message.resetPassword"></spring:message> </h1> <div> <br> <tr> <td><label><spring:message code="label.user.email"></spring:message></label></td> <td><input id="email" name="email" type="email" value="" /></td> </tr> <button type="submit" onclick="resetPass()"> <spring:message code="message.resetPassword"></spring:message> </button> </div> <br> <a href="<c:url value="registration.html" />"> <spring:message code="label.form.loginSignUp"></spring:message> </a> <br> <a href="<c:url value="login.html" />"> <spring:message code="label.form.loginLink"></spring:message> </a> </div> </div> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script> <script type="text/javascript"> function resetPass(){ var email = $("#email").val(); $.post("<c:url value="/user/resetPassword"></c:url>",{email: email} ,function(data){ if(data.indexOf("MailError") > -1) { window.location.href = "<c:url value="/emailError.html"></c:url>"; } else if(data.indexOf("InternalError") > -1){ window.location.href = "<c:url value="/login.html"> <c:param name="message" value="Error Occurred"/> </c:url>"; } else{ window.location.href = "<c:url value="/login.html"></c:url>" + "?message=" + data; } }); } </script> </body> </html>
We’ll enable the user to reset their password by adding a link to reset password in login.html:
Current Locale : ${pageContext.response.locale} <br> <a href="<c:url value="/user/registration" />"> <spring:message code="label.form.loginSignUp"></spring:message> </a> <br> <a href="<c:url value="/forgotPassword.html" />"> <spring:message code="message.resetPassword"></spring:message> </a>
4. Create the PasswordResetToken
Next, we’ll create the new PasswordResetToken and send it via email to the user:
@RequestMapping(value = "/user/resetPassword", method = RequestMethod.POST) public @ResponseBody String resetPassword( HttpServletRequest request, Model model, @RequestParam("email") String userEmail) throws JsonProcessingException, NoSuchMessageException { User user = userService.findUserByEmail(userEmail); if (user == null) { return new ObjectMapper().writeValueAsString( messages.getMessage("message.userNotFound", null, request.getLocale())); } String token = UUID.randomUUID().toString(); userService.createPasswordResetTokenForUser(user, token); String appUrl = "http://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath(); SimpleMailMessage email = constructResetTokenEmail(appUrl, request.getLocale(), token, user); mailSender.send(email); return new ObjectMapper().writeValueAsString( messages.getMessage("message.resetPasswordEmail", null, request.getLocale())); }
And here is method constructResetTokenEmail() – used to send an email with the reset token:
private SimpleMailMessage constructResetTokenEmail( String contextPath, Locale locale, String token, User user) { String url = contextPath + "/user/changePassword?id=" + user.getId() + "&token=" + token; String message = messages.getMessage("message.resetPassword", null, locale); SimpleMailMessage email = new SimpleMailMessage(); email.setTo(user.getEmail()); email.setSubject("Reset Password"); email.setText(message + " \r\n" + url); email.setFrom(env.getProperty("support.email")); return email; }
5. Process the PasswordResetToken
The user gets the email with the unique link for resetting their password, and clicks the link:
@RequestMapping(value = "/user/changePassword", method = RequestMethod.GET) public String changePassword( HttpServletRequest request, Model model, @RequestParam("id") long id, @RequestParam("token") String token) { Locale locale = request.getLocale(); PasswordResetToken passToken = userService.getPasswordResetToken(token); User user = passToken.getUser(); if (passToken == null || user.getId() != id) { String message = messages.getMessage("auth.message.invalidToken", null, locale); model.addAttribute("message", message); return "redirect:/login.html?lang=" + locale.getLanguage(); } Calendar cal = Calendar.getInstance(); if ((passToken.getExpiryDate().getTime() - cal.getTime().getTime()) <= 0) { model.addAttribute("message", messages.getMessage("auth.message.expired", null, locale)); return "redirect:/login.html?lang=" + locale.getLanguage(); } Authentication auth = new UsernamePasswordAuthenticationToken( user, null, userDetailsService.loadUserByUsername(user.getEmail()).getAuthorities()); SecurityContextHolder.getContext().setAuthentication(auth); return "redirect:/updatePassword.html?lang=" + locale.getLanguage(); }
As you can see – if the token is valid, the user will be authorized to change their password, and directed to a page to update their password.
6. Change Password
At this point, the user sees the simple Password Reset page – where the only possible option is to provide a new password:
6.1. updatePassword.html
<!DOCTYPE html> <%@ page contentType="text/html;charset=UTF-8" language="java"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%> <%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%> <%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags"%> <%@ page session="false"%> <html> <head> <link href="<c:url value="/resources/bootstrap.css" />" rel="stylesheet"> <meta http-equiv="Content-Type" content="text/html; charset=US-ASCII"> <title><spring:message code="message.updatePassword"></spring:message></title> </head> <body> <sec:authorize access="hasRole('READ_PRIVILEGE')"> <div class="container"> <div class="span12"> <h1> <spring:message code="message.resetYourPassword"></spring:message> </h1> <form:form action="user/savePassword" method="POST" enctype="utf8"> <br> <tr> <td><label> <spring:message code="label.user.password"></spring:message> </label></td> <td><input id="pass" name="password" type="password" value="" /></td> </tr> <tr> <td><label> <spring:message code="label.user.confirmPass"></spring:message> </label></td> <td> <input id="passConfirm" type="password" value="" /> <span id="error" class="alert alert-error" style="display:none"> <spring:message code="PasswordMatches.user"></spring:message> </span> </td> </tr> <br><br> <button type="submit"> <spring:message code="message.updatePassword"></spring:message> </button> </form:form> </div> </div> </sec:authorize> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script> <script type="text/javascript"> $(document).ready(function() { $('form').on('submit', function(e){ var valid = $("#pass").val() == $("#passConfirm").val(); if(!valid) { e.preventDefault(); $("#error").show(); } }); }); </script> </body> </html>
6.2. Save User Password
Finally, when the previous form is submitted – the new user password is saved:
@RequestMapping(value = "/user/savePassword", method = RequestMethod.POST) @PreAuthorize("hasRole('READ_PRIVILEGE')") public String savePassword( HttpServletRequest request, Model model, @RequestParam("password") String password) { Locale locale = request.getLocale(); User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); userService.changeUserPassword(user, password); model.addAttribute("message", messages.getMessage("message.resetPasswordSuc", null, locale)); return "redirect:/login.html?lang=" + locale; }
7. Conclusion
In this article we implemented a simple but very useful feature for a mature Authentication process – the option to reset your own password, as a user of the system.
The full implementation of this tutorial can be found in the github project – this is an Eclipse based project, so it should be easy to import and run as it is.