I’m migrating from Spring 5 with Spring Security 3 to Spring Boot 3 with Spring Security 6.
I have to do several “things” when people call a page first, or e.g. log missed logins and save failed retries to database, lock users then and so on.
The application is a simple HTML multipage webapp with PostgreSQL 16 and Thymeleaf.
I made it to can successful login valid users.
But, when the authentication fails, my AuthenticationProvider is called twice and either the failing call ends in a endless loop, (with calling the super-method in the handler as documented) or getting an empty result page (without calling the super-method).
Here is my code
SecurityConfig.java:
@Slf4j
@NoArgsConstructor
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {
@Autowired
MySuccessHandler successHandler;
@Autowired
CustomAuthenticationProvider authProvider;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
log.error("#### Security config called");
CsrfTokenRequestAttributeHandler requestHandler = new CsrfTokenRequestAttributeHandler();
// set the name of the attribute the CsrfToken will be populated on
requestHandler.setCsrfRequestAttributeName(null);
CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setEncoding("UTF-8");
filter.setForceEncoding(true);
http.addFilterBefore(
new CustomAuthenticationFilter(), BasicAuthenticationFilter.class);
http
.addFilterBefore(filter, CsrfFilter.class)
.httpBasic(withDefaults())
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers(
"/static/**",
"/register*", //
"/logout*", //
[.... many others ....]
"/favicon.ico",//
"/login",//
"/webjars/**"
)
.permitAll()
.anyRequest()
.authenticated()
)
.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.ignoringRequestMatchers(
"/rest/*",
"/webjars**",
"/favicon.ico"
)
.csrfTokenRequestHandler(requestHandler)
)
.formLogin((form) -> form
.loginPage("/login")
.permitAll()
.successHandler(successHandler)
.loginProcessingUrl("/login")
.failureHandler(new FailureHandler("/login"))
)
.exceptionHandling(n -> n.accessDeniedPage("/login"))
.logout((logout) -> logout.permitAll()
.logoutUrl("/logout").permitAll().logoutSuccessUrl("/login") //
)
.authenticationProvider(authProvider)
;
return http.build();
}
}
FailureHandler.java:
public class FailureHandler
extends ForwardAuthenticationFailureHandler {
public FailureHandler(String forwardUrl) {
//super(forwardUrl); // <--- tried all combinations
super("/");
}
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
if (exception instanceof WaitForActivationAuthException) {
request.setAttribute(Constants.WAIT_FOR_ACTIVATION_INFO, Constants.WAIT_FOR_ACTIVATION_INFO);
}
if (exception instanceof BadCredentialsException || exception instanceof UsernameNotFoundException) {
request.setAttribute(Constants.BAD_CREDENTIALS, Constants.BAD_CREDENTIALS);
}
//super.onAuthenticationFailure(request, response, exception); // <--- tried with and without
}
}
CustomAuthenticationProvider.java
@Slf4j
@Component
public class CustomAuthenticationProvider
implements AuthenticationProvider {
final UserDetailsServiceImpl userDetailsServiceImpl;
final UserLoginFailedService failService;
final UserService userService;
final UserControllerService userControllerService;
public CustomAuthenticationProvider(
UserDetailsServiceImpl userDetailsServiceImpl,
UserLoginFailedService failService,
UserService userService,
UserControllerService userControllerService
) {
this.userDetailsServiceImpl = userDetailsServiceImpl;
this.failService = failService;
this.userService = userService;
this.userControllerService = userControllerService;
}
@Override
public Authentication authenticate(Authentication auth) throws AuthenticationException {
log.error("here: *** customAuthentication::authenticate ***"); // <--- appears twice in log when aut fails
try {
String username = auth.getName();
String password = // [doTheCipher]
UserDetails a = userDetailsServiceImpl.loadUserByUsername(username);
log.info("User login found: {}", a.getUsername());
User user = userService.getUserByLogin(username);
if (!password.equals(a.getPassword())) {
log.error("Username oder password wrong; {}", a.getUsername());
failService.loginFailed(user.getUserId());
throw new BadCredentialsException("authentication failed");
}
log.info("Login successful: {}", a.getUsername());
userControllerService.successfulLogin(user);
return new UsernamePasswordAuthenticationToken(
a.getUsername(),
password,
a.getAuthorities()
);
} catch (ShowErrorException e) {
throw new TechnicalErrorAuthException(e.getMessage());
}
}
@Override
public boolean supports(Class<?> auth) {
return auth.equals(UsernamePasswordAuthenticationToken.class);
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
log.error("configure Auth-provider src **# ?");
auth.authenticationProvider(this);
}
CustomAuthenticationFilter.java
public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
System.out.println("***** tid in filter: " + request.getParameter("tid"));
UsernamePasswordAuthenticationToken authRequest = getAuth(request);
setDetails(request, authRequest);
return getAuthenticationManager().authenticate(authRequest);
}
private UsernamePasswordAuthenticationToken getAuth(final HttpServletRequest request) {
String username = obtainUsername(request);
String password = obtainPassword(request);
return new UsernamePasswordAuthenticationToken(username, password);
}
What do I have to do to solve the loop-to-death rep. the empty result page?
2