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