Spring Security - Setzen Sie Ihr Passwort zurück

Spring Security - Setzen Sie Ihr Passwort zurück

1. Überblick

In diesem Tutorial setzen wir die laufendenRegistration with Spring Security series mit einem Blick aufthe basic “I forgot my password” feature fort, damit der Benutzer sein eigenes Passwort bei Bedarf sicher zurücksetzen kann.

2. Das Passwort-Reset-Token

Beginnen wir mit der Erstellung einerPasswordResetToken-Entität, um sie zum Zurücksetzen des Benutzerkennworts zu verwenden:

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

Wenn ein Zurücksetzen des Passworts ausgelöst wird, wird ein Token erstellt unda special link containing this token will be emailed to the user.

Das Token und der Link sind nur für einen festgelegten Zeitraum gültig (in diesem Beispiel 24 Stunden).

3. forgotPassword.html

Die erste Seite des Prozesses istthe “I forgot my password” page. Hier wird der Benutzer zur Eingabe seiner E-Mail-Adresse aufgefordert, damit der eigentliche Rücksetzvorgang gestartet werden kann.

Erstellen wir also ein einfachesforgotPassword.html, in dem der Benutzer nach einer E-Mail-Adresse gefragt wird:



    

reset

registration login

Wir müssen jetzt von der Anmeldeseite aus auf diese neue Seite "reset password" verlinken:

4. Erstellen Sie diePasswordResetToken

Beginnen wir mit der Erstellung der neuenPasswordResetTokenund senden sie per E-Mail an den Benutzer:

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

Und hier ist MethodecreatePasswordResetTokenForUser():

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

Und hier ist MethodeconstructResetTokenEmail() - verwendet, um eine E-Mail mit dem Reset-Token zu senden:

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

Beachten Sie, wie wir ein einfaches ObjektGenericResponse verwendet haben, um unsere Antwort an den Client darzustellen:

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. Verarbeiten Sie diePasswordResetToken

Der Benutzer erhält die E-Mail mit dem eindeutigen Link zum Zurücksetzen seines Passworts und klickt auf den 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();
}

Und hier ist die Methode vonvalidatePasswordResetToken():

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

Wie Sie sehen können, ist der Benutzer berechtigt, sein Kennwort zu ändern, indem er ihmCHANGE_PASSWORD_PRIVILEGE gewährt, und ihn auf eine Seite weiterzuleiten, um sein Kennwort zu aktualisieren, wenn das Token gültig ist.

Der interessante Hinweis hier ist: Dieses neue Privileg kann nur zum Ändern des Passworts verwendet werden (wie der Name schon sagt). Daher ist es sicher, es dem Benutzer programmgesteuert zu erteilen.

6. Passwort ändern

Zu diesem Zeitpunkt sieht der Benutzer die einfache SeitePassword Reset - wobei die einzig mögliche Optionprovide a new password ist:

6.1. updatePassword.html



reset

6.2. Benutzerkennwort speichern

Wenn die vorherige Post-Anfrage gesendet wird, wird das neue Benutzerpasswort gespeichert:

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

Und hier ist die Methode vonchangeUserPassword():

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

Beachten Sie, dass wir Aktualisierungs- und Kennwortanforderungen wie folgt sichern:

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

7. Fazit

In diesem Artikel haben wir eine einfache, aber sehr nützliche Funktion für einen ausgereiften Authentifizierungsprozess implementiert - die Option, Ihr eigenes Kennwort als Benutzer des Systems zurückzusetzen.

Diefull implementation dieses Tutorials finden Sie inthe GitHub project - dies ist ein Eclipse-basiertes Projekt, daher sollte es einfach zu importieren und auszuführen sein.