If you have a few years of experience in the Java ecosystem, and you're interested in sharing that experience with the community (and getting paid for your work of course), have a look at the "Write for Us" page. Cheers. Eugen

I just announced the newSpring Security 5 modules (primarily focused on OAuth2) in the course:

>> CHECK OUT LEARN SPRING SECURITY


This article is part of a series:
Spring Security Registration TutorialThe Registration Process With Spring SecurityRegistration – Activate a New Account by EmailSpring Security Registration – Resend Verification EmailRegistration with Spring Security – Password EncodingThe Registration API becomes RESTful
Spring Security – Reset Your Password
Registration – Password Strength and RulesUpdating your Password

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 user’s 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:

<html>
<body>
    <h1 th:text="#{message.resetPassword}">reset</h1>

    <label th:text="#{label.user.email}">email</label>
    <input id="email" name="email" type="email" value="" />
    <button type="submit" onclick="resetPass()" 
      th:text="#{message.resetPassword}">reset</button>

<a th:href="@{/registration.html}" th:text="#{label.form.loginSignUp}">
    registration
</a>
<a th:href="@{/login}" th:text="#{label.form.loginLink}">login</a>

<script src="jquery.min.js"></script>
<script th:inline="javascript">
var serverContext = [[@{/}]];
function resetPass(){
    var email = $("#email").val();
    $.post(serverContext + "user/resetPassword",{email: email} ,
      function(data){
          window.location.href = 
           serverContext + "login?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?message=" + data.responseJSON.message;
        }
    });
}

</script>
</body>

</html>

We now need to link to this new “reset password” page from the login page:

<a th:href="@{/forgetPassword.html}" 
  th:text="#{message.resetPassword}">reset</a>

4. Create the PasswordResetToken

Let’s start by creating the new PasswordResetToken and send it via email to the user:

@RequestMapping(value = "/user/resetPassword", 
                method = RequestMethod.POST)
@ResponseBody
public GenericResponse resetPassword(HttpServletRequest request, 
  @RequestParam("email") String userEmail) {
    User user = userService.findUserByEmail(userEmail);
    if (user == null) {
        throw new UserNotFoundException();
    }
    String token = UUID.randomUUID().toString();
    userService.createPasswordResetTokenForUser(user, token);
    mailSender.send(constructResetTokenEmail(getAppUrl(request), 
      request.getLocale(), token, user));
    return new GenericResponse(
      messages.getMessage("message.resetPasswordEmail", null, 
      request.getLocale()));
}

And here is method createPasswordResetTokenForUser():

public void createPasswordResetTokenForUser(User user, String token) {
    PasswordResetToken myToken = new PasswordResetToken(token, user);
    passwordTokenRepository.save(myToken);
}

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);
    return constructEmail("Reset Password", message + " \r\n" + url, user);
}

private SimpleMailMessage constructEmail(String subject, String body, 
  User user) {
    SimpleMailMessage email = new SimpleMailMessage();
    email.setSubject(subject);
    email.setText(body);
    email.setTo(user.getEmail());
    email.setFrom(env.getProperty("support.email"));
    return email;
}

Note how we used a simple object GenericResponse to represent our response to the client:

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

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 showChangePasswordPage(Locale locale, Model model, 
  @RequestParam("id") long id, @RequestParam("token") String token) {
    String result = securityService.validatePasswordResetToken(id, token);
    if (result != null) {
        model.addAttribute("message", 
          messages.getMessage("auth.message." + result, null, locale));
        return "redirect:/login?lang=" + locale.getLanguage();
    }
    return "redirect:/updatePassword.html?lang=" + locale.getLanguage();
}

And here is  validatePasswordResetToken() method:

public String validatePasswordResetToken(long id, String token) {
    PasswordResetToken passToken = 
      passwordTokenRepository.findByToken(token);
    if ((passToken == null) || (passToken.getUser()
        .getId() != id)) {
        return "invalidToken";
    }

    Calendar cal = Calendar.getInstance();
    if ((passToken.getExpiryDate()
        .getTime() - cal.getTime()
        .getTime()) <= 0) {
        return "expired";
    }

    User user = passToken.getUser();
    Authentication auth = new UsernamePasswordAuthenticationToken(
      user, null, Arrays.asList(
      new SimpleGrantedAuthority("CHANGE_PASSWORD_PRIVILEGE")));
    SecurityContextHolder.getContext().setAuthentication(auth);
    return null;
}

As you can see – if the token is valid, the user will be authorized to change their password by granting them a CHANGE_PASSWORD_PRIVILEGE, and direct them to a page to update their password.

The interesting note here is – this new privilege will only be usable to change the password (as the name implies) – and so granting it programmatically to the user is safe.

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

<html>
<body>
<div sec:authorize="hasAuthority('CHANGE_PASSWORD_PRIVILEGE')">
    <h1 th:text="#{message.resetYourPassword}">reset</h1>
    <form>
        <label th:text="#{label.user.password}">password</label>
        <input id="password" name="newPassword" type="password" value="" />

        <label th:text="#{label.user.confirmPass}">confirm</label>
        <input id="matchPassword" type="password" value="" />
        
        <div id="globalError" style="display:none" 
          th:text="#{PasswordMatches.user}">error</div>
        <button type="submit" onclick="savePass()" 
          th:text="#{message.updatePassword}">submit</button>
    </form>
               
<script th:inline="javascript">
var serverContext = [[@{/}]];
$(document).ready(function () {
    $('form').submit(function(event) {
        savePass(event);
    });
    
    $(":password").keyup(function(){
        if($("#password").val() != $("#matchPassword").val()){
            $("#globalError").show().html(/*[[#{PasswordMatches.user}]]*/);
        }else{
            $("#globalError").html("").hide();
        }
    });
});

function savePass(event){
    event.preventDefault();
    if($("#password").val() != $("#matchPassword").val()){
        $("#globalError").show().html(/*[[#{PasswordMatches.user}]]*/);
        return;
    }
    var formData= $('form').serialize();
    $.post(serverContext + "user/savePassword",formData ,function(data){
        window.location.href = serverContext + "login?message="+data.message;
    })
    .fail(function(data) {
        if(data.responseJSON.error.indexOf("InternalError") > -1){
        	window.location.href = serverContext + "login?message=" + data.responseJSON.message;
        }
        else{
            var errors = $.parseJSON(data.responseJSON.message);
            $.each( errors, function( index,item ){
                $("#globalError").show().html(item.defaultMessage);
            });
            errors = $.parseJSON(data.responseJSON.error);
            $.each( errors, function( index,item ){
                $("#globalError").show().append(item.defaultMessage+"<br/>");
            });
        }
    });
}
</script>    
</div>
</body>
</html>

6.2. Save User Password

Finally, when the previous post request is submitted – the new user password is saved:

@RequestMapping(value = "/user/savePassword", method = RequestMethod.POST)
@ResponseBody
public GenericResponse savePassword(Locale locale, 
  @Valid PasswordDto passwordDto) {
    User user = 
      (User) SecurityContextHolder.getContext()
                                  .getAuthentication().getPrincipal();
    
    userService.changeUserPassword(user, passwordDto.getNewPassword());
    return new GenericResponse(
      messages.getMessage("message.resetPasswordSuc", null, locale));
}

And here is changeUserPassword() method:

public void changeUserPassword(User user, String password) {
    user.setPassword(passwordEncoder.encode(password));
    repository.save(user);
}

Note that we are securing update and save password requests – as follows:

protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
        .antMatchers("/user/updatePassword*",
                     "/user/savePassword*",
                     "/updatePassword*")
        .hasAuthority("CHANGE_PASSWORD_PRIVILEGE")
...

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.

I just announced the new Spring Security 5 modules (primarily focused on OAuth2) in the course:

>> CHECK OUT LEARN SPRING SECURITY

Sort by:   newest | oldest | most voted
Andreia Santos
Guest

Hello

I had some problems while trying to run the application, please if you could help…
To run the project, I just have to change the files email.properties and create the database registration_02? I can run using Jetty;run right?

Thank you very much

Eugen Paraschiv
Guest

That is pretty much it. On the persistence side of things, also make sure that you also create the user that is defined in persistence.properties and give it the right permissions – so that the app is able to access the DB. If you run into any kind of problem, go ahead and open an issue over on github and I’ll take a look. Cheers,
Eugen.

Marcel Brimus
Guest

Hello,

Thanks for this awesome tutorial.
I have one question, wont this line:

Authentication auth = new UsernamePasswordAuthenticationToken(
user, null, userDetailsService.loadUserByUsername(user.getEmail()).getAuthorities());
SecurityContextHolder.getContext().setAuthentication(auth);

make user Authenticated for /updatePassword.html and thus for every other page where user needs to have READ_PRIVILEGE? If so how can i “de-authenticate” user when he does another request?

Illia Trach
Guest

SecurityContextHolder.getContext().setAuthentication(null);

David Clark
Guest

Very nice tutorial, but I was unable to find the referenced source code on GitHub. Is it still available somewhere?

Eugen Paraschiv
Guest

Hey David – yeah, I updated/moved the code on github yesterday – I’m updating the links now. Cheers,
Eugen.

Dre
Guest

Thank you for your tutorial.

Ranjan Kumar
Guest

I dont know the application is working or not but my server is crashing every time ….shit

Eugen Paraschiv
Guest

Hey Ranjan – that’s interesting. When you say “crashes” – you’ll have to be more specific than that. Is there a stack trace? Does the JVM process dies? What does the crash look like exactly?

Ranjan Kumar
Guest

Hey actually the problem is on IDE I changed my work space and now its fine but help me when I am using the gmail smtp there is a problem while connecting MySql server.. If its possible tell me how to configure gmail smtp into your application.. Thanks dude.

Eugen Paraschiv
Guest

Hey Ranjan – so, the configuration would look like this:

smtp.host=smtp.gmail.com
smtp.port=465
smtp.protocol=smtps
[email protected]
spring.mail.password=TODO

Hope it helps. Cheers,
Eugen.

wpDiscuz