I’m migrating from
Spring 5 with Spring Security 3
to
Spring Boot 3 with Spring Security 6.
I’ve to do several “things” when people call 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 sucessful 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 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);
}
What do i have to do to solve the loop-to-death rep. the empty result page?
Thanks in advice …