Spring Security - Réinitialisez votre mot de passe

Cet article fait partie d'une série: • Didacticiel d'enregistrement Spring Security

• Le processus d'inscription avec Spring Security

• Inscription - Activez un nouveau compte par e-mail

• Enregistrement Spring Security - Renvoyer l'e-mail de vérification

• Enregistrement avec Spring Security - Codage du mot de passe

• L'API d'inscription devient RESTful

• Spring Security - Réinitialisez votre mot de passe (article actuel) • Inscription - Force et règles du mot de passe

• Mise à jour de votre mot de passe

1. Vue d'ensemble

Dans ce tutoriel - nous continuons le cours Enregistrement auprès du printemps de sécurité série avec un regard sur la base «J'ai oublié mon mot de passe fonction » - afin que l'utilisateur peut réinitialiser en toute sécurité leur propre mot de passe quand ils ont besoin.

2. Demandez la réinitialisation de votre mot de passe

Un flux de réinitialisation de mot de passe démarre généralement lorsque l'utilisateur clique sur une sorte de bouton «réinitialiser» sur la page de connexion. Ensuite, nous pouvons demander à l'utilisateur son adresse e-mail ou d'autres informations d'identification. Une fois confirmé, nous pouvons générer un token et envoyer un email à l'utilisateur.

Le diagramme suivant visualise le flux que nous allons implémenter dans cet article:

3. Le jeton de réinitialisation de mot de passe

Commençons par créer une entité PasswordResetToken pour l'utiliser pour réinitialiser le mot de passe de l'utilisateur:

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

Lorsqu'une réinitialisation de mot de passe est déclenchée, un jeton sera créé et un lien spécial contenant ce jeton sera envoyé par courrier électronique à l'utilisateur .

Le jeton et le lien ne seront valides que pendant une période de temps définie (24 heures dans cet exemple).

4. ForgotPassword.html

La première page du processus est la page « J'ai oublié mon mot de passe » - où l'utilisateur est invité à entrer son adresse e-mail pour que le processus de réinitialisation puisse démarrer.

Alors - créons un simple ForgotPassword.html demandant à l'utilisateur une adresse e-mail:

reset

email reset registration login 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; } }); }

Nous devons maintenant créer un lien vers cette nouvelle page de « réinitialisation du mot de passe » à partir de la page de connexion:

reset

5. Créez le PasswordResetToken

Commençons par créer le nouveau PasswordResetToken et envoyez-le par e-mail à l'utilisateur:

@PostMapping("/user/resetPassword") 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())); }

Et voici la méthode createPasswordResetTokenForUser () :

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

Et voici la méthode constructResetTokenEmail () - utilisée pour envoyer un email avec le jeton de réinitialisation:

private SimpleMailMessage constructResetTokenEmail( String contextPath, Locale locale, String token, User user) { String url = contextPath + "/user/changePassword?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; }

Notez comment nous avons utilisé un objet simple GenericResponse pour représenter notre réponse au 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; } }

6. Vérifiez le PasswordResetToken

Une fois que l'utilisateur clique sur le lien dans son e-mail, le point de terminaison user / changePassword :

  • vérifie que le jeton est valide et
  • présente à l'utilisateur la page updatePassword , où il peut saisir un nouveau mot de passe

Le nouveau mot de passe et le jeton sont ensuite transmis au point de terminaison user / savePassword :

L'utilisateur reçoit l'e-mail avec le lien unique pour réinitialiser son mot de passe, et clique sur le lien:

@GetMapping("/user/changePassword") public String showChangePasswordPage(Locale locale, Model model, @RequestParam("token") String token) { String result = securityService.validatePasswordResetToken(token); if(result != null) { String message = messages.getMessage("auth.message." + result, null, locale); return "redirect:/login.html?lang=" + locale.getLanguage() + "&message=" + message; } else { model.addAttribute("token", token); return "redirect:/updatePassword.html?lang=" + locale.getLanguage(); } }

Et voici la méthode validatePasswordResetToken () :

public String validatePasswordResetToken(String token) { final PasswordResetToken passToken = passwordTokenRepository.findByToken(token); return !isTokenFound(passToken) ? "invalidToken" : isTokenExpired(passToken) ? "expired" : null; } private boolean isTokenFound(PasswordResetToken passToken) { return passToken != null; } private boolean isTokenExpired(PasswordResetToken passToken) { final Calendar cal = Calendar.getInstance(); return passToken.getExpiryDate().before(cal.getTime()); }

7. Changer le mot de passe

At this point, the user sees the simple Password Reset page – where the only possible option is to provide a new password:

7.1. updatePassword.html

reset

password confirm token error submit 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+"

"); }); } }); }

Note that we show the reset token and pass it as a POST parameter in the following call to save the password.

7.2. Save the Password

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

@PostMapping("/user/savePassword") public GenericResponse savePassword(final Locale locale, @Valid PasswordDto passwordDto) { String result = securityUserService.validatePasswordResetToken(passwordDto.getToken()); if(result != null) { return new GenericResponse(messages.getMessage( "auth.message." + result, null, locale)); } Optional user = userService.getUserByPasswordResetToken(passwordDto.getToken()); if(user.isPresent()) { userService.changeUserPassword(user.get(), passwordDto.getNewPassword()); return new GenericResponse(messages.getMessage( "message.resetPasswordSuc", null, locale)); } else { return new GenericResponse(messages.getMessage( "auth.message.invalid", null, locale)); } }

And here is the changeUserPassword() method:

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

And the PasswordDto:

public class PasswordDto { private String oldPassword; private String token; @ValidPassword private String newPassword; } 

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

L' implémentation complète de ce didacticiel se trouve dans le projet GitHub - il s'agit d'un projet basé sur Eclipse, il devrait donc être facile à importer et à exécuter tel quel.

Suivant » Inscription - Force et règles du mot de passe « Précédent L'API d'inscription devient RESTful