diff --git a/servlet/spring-boot/java/authentication/username-password/compromised-password-checker/src/main/java/org/example/compromisedpasswordchecker/ResetPasswordController.java b/servlet/spring-boot/java/authentication/username-password/compromised-password-checker/src/main/java/org/example/compromisedpasswordchecker/ResetPasswordController.java index c08f63b..7d05083 100644 --- a/servlet/spring-boot/java/authentication/username-password/compromised-password-checker/src/main/java/org/example/compromisedpasswordchecker/ResetPasswordController.java +++ b/servlet/spring-boot/java/authentication/username-password/compromised-password-checker/src/main/java/org/example/compromisedpasswordchecker/ResetPasswordController.java @@ -16,14 +16,11 @@ package org.example.compromisedpasswordchecker; -import org.springframework.security.authentication.password.CompromisedPasswordChecker; -import org.springframework.security.authentication.password.CompromisedPasswordDecision; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UsernameNotFoundException; +import jakarta.servlet.http.HttpSession; + import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; @Controller @@ -33,41 +30,20 @@ class ResetPasswordController { private final PasswordEncoder passwordEncoder; - private final CompromisedPasswordChecker passwordChecker; - - ResetPasswordController(InMemoryUserDetailsManager userDetailsManager, PasswordEncoder passwordEncoder, - CompromisedPasswordChecker passwordChecker) { + ResetPasswordController(InMemoryUserDetailsManager userDetailsManager, PasswordEncoder passwordEncoder) { this.userDetailsManager = userDetailsManager; this.passwordEncoder = passwordEncoder; - this.passwordChecker = passwordChecker; - } - - @GetMapping("/reset-password") - String resetPasswordPage() { - return "reset-password"; } @PostMapping("/reset-password") - String resetPassword(ResetPasswordRequest resetPasswordRequest) { - UserDetails user = this.userDetailsManager.loadUserByUsername(resetPasswordRequest.username()); - if (user == null) { - throw new UsernameNotFoundException("User not found"); - } - CompromisedPasswordDecision compromisedPassword = this.passwordChecker - .check(resetPasswordRequest.newPassword()); - if (compromisedPassword.isCompromised()) { - return "redirect:/reset-password?error=compromised_password"; - } - boolean oldPasswordMatches = this.passwordEncoder.matches(resetPasswordRequest.currentPassword(), - user.getPassword()); - if (!oldPasswordMatches) { - return "redirect:/reset-password?error=invalid_password"; - } - this.userDetailsManager.updatePassword(user, this.passwordEncoder.encode(resetPasswordRequest.newPassword())); - return "redirect:/login"; + String resetPassword(ResetPasswordRequest resetPasswordRequest, HttpSession session) { + String newPassword = this.passwordEncoder.encode(resetPasswordRequest.newPassword()); + this.userDetailsManager.changePassword(resetPasswordRequest.currentPassword(), newPassword); + session.removeAttribute("compromised_password"); + return "redirect:/"; } - record ResetPasswordRequest(String username, String currentPassword, String newPassword) { + record ResetPasswordRequest(String currentPassword, String newPassword) { } } diff --git a/servlet/spring-boot/java/authentication/username-password/compromised-password-checker/src/main/java/org/example/compromisedpasswordchecker/SecurityConfig.java b/servlet/spring-boot/java/authentication/username-password/compromised-password-checker/src/main/java/org/example/compromisedpasswordchecker/SecurityConfig.java index 8a1b92d..8f17807 100644 --- a/servlet/spring-boot/java/authentication/username-password/compromised-password-checker/src/main/java/org/example/compromisedpasswordchecker/SecurityConfig.java +++ b/servlet/spring-boot/java/authentication/username-password/compromised-password-checker/src/main/java/org/example/compromisedpasswordchecker/SecurityConfig.java @@ -21,26 +21,29 @@ import java.io.IOException; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.password.CompromisedPasswordChecker; -import org.springframework.security.authentication.password.CompromisedPasswordException; +import org.springframework.security.authentication.password.CompromisedPasswordDecision; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.core.AuthenticationException; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.core.Authentication; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.crypto.factory.PasswordEncoderFactories; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.provisioning.InMemoryUserDetailsManager; -import org.springframework.security.web.DefaultRedirectStrategy; -import org.springframework.security.web.RedirectStrategy; import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; +import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; import org.springframework.security.web.authentication.password.HaveIBeenPwnedRestApiPasswordChecker; @Configuration(proxyBeanMethods = false) +@EnableWebSecurity public class SecurityConfig { @Bean @@ -52,15 +55,17 @@ public class SecurityConfig { .anyRequest().authenticated() ) .formLogin((login) -> login - .failureHandler(new CompromisedPasswordAuthenticationFailureHandler()) + .successHandler(new CompromisedPasswordAwareAuthenticationSuccessHandler()) ); // @formatter:on return http.build(); } - @Bean - CompromisedPasswordChecker compromisedPasswordChecker() { - return new HaveIBeenPwnedRestApiPasswordChecker(); + @Autowired + void configure(AuthenticationManagerBuilder builder) { + // @formatter:off + builder.eraseCredentials(false); // Do not clear credentials after authentication, so we have access to passwords on success handlers + // @formatter:on } @Bean @@ -78,21 +83,22 @@ public class SecurityConfig { return new InMemoryUserDetailsManager(user); } - static class CompromisedPasswordAuthenticationFailureHandler implements AuthenticationFailureHandler { + static class CompromisedPasswordAwareAuthenticationSuccessHandler implements AuthenticationSuccessHandler { - private final SimpleUrlAuthenticationFailureHandler defaultFailureHandler = new SimpleUrlAuthenticationFailureHandler( - "/login?error"); + private final AuthenticationSuccessHandler successHandler = new SimpleUrlAuthenticationSuccessHandler("/"); - private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); + private final CompromisedPasswordChecker compromisedPasswordChecker = new HaveIBeenPwnedRestApiPasswordChecker(); @Override - public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, - AuthenticationException exception) throws IOException, ServletException { - if (exception instanceof CompromisedPasswordException) { - this.redirectStrategy.sendRedirect(request, response, "/reset-password"); - return; + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, + Authentication authentication) throws IOException, ServletException { + CompromisedPasswordDecision decision = this.compromisedPasswordChecker + .check((String) authentication.getCredentials()); + if (decision.isCompromised()) { + HttpSession session = request.getSession(false); + session.setAttribute("compromised_password", true); } - this.defaultFailureHandler.onAuthenticationFailure(request, response, exception); + this.successHandler.onAuthenticationSuccess(request, response, authentication); } } diff --git a/servlet/spring-boot/java/authentication/username-password/compromised-password-checker/src/main/resources/templates/index.html b/servlet/spring-boot/java/authentication/username-password/compromised-password-checker/src/main/resources/templates/index.html index a24251a..5eeec72 100644 --- a/servlet/spring-boot/java/authentication/username-password/compromised-password-checker/src/main/resources/templates/index.html +++ b/servlet/spring-boot/java/authentication/username-password/compromised-password-checker/src/main/resources/templates/index.html @@ -1,5 +1,5 @@ - +
This is a secured page
+