Spring Security - Redefinir sua senha

Spring Security - Redefinir sua senha

1. Visão geral

Neste tutorial - estamos continuando oRegistration with Spring Security series em andamento com uma olhada emthe basic “I forgot my password” feature - para que o usuário possa redefinir com segurança sua própria senha quando precisar.

2. O token de redefinição de senha

Vamos começar criando uma entidadePasswordResetToken para usá-la para redefinir a senha do usuário:

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

Quando uma redefinição de senha é acionada - um token será criado ea special link containing this token will be emailed to the user.

O token e o link serão válidos apenas por um período definido (24 horas neste exemplo).

3. forgotPassword.html

A primeira página do processo éthe “I forgot my password” page - onde o usuário é solicitado a fornecer seu endereço de e-mail para que o processo de reinicialização real comece.

Então - vamos criar umforgotPassword.html simples pedindo ao usuário um endereço de e-mail:



    

reset

registration login

Agora precisamos criar um link para esta nova página “reset password” na página de login:

4. Crie oPasswordResetToken

Vamos começar criando o novoPasswordResetToken e enviá-lo por e-mail ao usuário:

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

E aqui está o métodocreatePasswordResetTokenForUser():

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

E aqui está o métodoconstructResetTokenEmail() - usado para enviar um e-mail com o token de redefinição:

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

Observe como usamos um objeto simplesGenericResponse para representar nossa resposta ao cliente:

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. Processe oPasswordResetToken

O usuário recebe o email com o link exclusivo para redefinir sua senha e clica no 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();
}

E aqui está o métodovalidatePasswordResetToken():

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

Como você pode ver - se o token for válido, o usuário será autorizado a alterar sua senha, concedendo-lhe umCHANGE_PASSWORD_PRIVILEGE, e direcionando-o para uma página para atualizar sua senha.

A observação interessante aqui é - esse novo privilégio só poderá ser usado para alterar a senha (como o nome indica) - e, portanto, conceder a segurança de forma programática ao usuário é seguro.

6. Mudar senha

Neste ponto, o usuário vê a páginaPassword Reset simples - onde a única opção possível éprovide a new password:

6.1. updatePassword.html



reset

6.2. Salvar senha de usuário

Por fim, quando a solicitação de postagem anterior é enviada - a nova senha do usuário é salva:

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

E aqui está o métodochangeUserPassword():

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

Observe que estamos protegendo a atualização e salvando as solicitações de senha - da seguinte maneira:

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

7. Conclusão

Neste artigo, implementamos um recurso simples, mas muito útil, para um processo maduro de autenticação - a opção de redefinir sua própria senha, como usuário do sistema.

Ofull implementation deste tutorial pode ser encontrado emthe GitHub project - este é um projeto baseado em Eclipse, portanto, deve ser fácil de importar e executar como está.