In my application I have implemented custom otp authentication. It was working correctly with Spring Security 5 but after updating to boot3 and Spring Security 6 it stopped working. Authentication is working just fine but it seems that the authenticated object is not stored in context and subsequent requests responds with 401.
The security configuration looks like this:
@Bean("otpSecurityFilterConfiguration") @Order(1) public SecurityFilterChain otpSecurityFilterChainConfiguration(HttpSecurity http) throws Exception { OtpAuthenticationFilter otpAuthenticationFilter = new OtpAuthenticationFilter(createOtpAuthenticationManager(), authenticationSuccessHandler, authenticationFailureHandler); http .securityMatcher(new AntPathRequestMatcher("/**")) .addFilterBefore(otpAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) .formLogin(form -> form.loginProcessingUrl("/login")) .cors(Customizer.withDefaults()) .csrf(csrf -> csrf.disable()) .exceptionHandling(handler -> handler.authenticationEntryPoint(otpAuthenticationEntryPoint)) .authorizeHttpRequests(requests -> requests .requestMatchers("/auth/sendOtp").permitAll() .anyRequest().authenticated()) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)) .headers(headers -> headers .contentTypeOptions(Customizer.withDefaults())); return http.build(); } private AuthenticationManager createOtpAuthenticationManager() { return new ProviderManager(otpAuthProvider); }
And the otpAuthenticationFilter which extends AbstractAuthenticationProcessingFilter
captures the /login request, creates authenticated token and process further
@Slf4j public class OtpAuthenticationFilter extends AbstractAuthenticationProcessingFilter { @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) { if (postOnly && !request.getMethod().equals("POST")) { throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod()); } String code = request.getParameter("code"); String uid = request.getParameter("uid"); String email = request.getParameter("email"); log.info("Received authentication request with email: {} and uid: {}", email, uid); String sessionKey = OtpAuthUtils.getSessionKey(uid, email); OtpCode sessionCode = (OtpCode) request.getSession().getAttribute(sessionKey); validateOtpCode(sessionCode, code, sessionKey, request); log.info("Otp code validated successfully. User {} is now authenticated", email); OtpAuthenticationToken authRequest = new OtpAuthenticationToken(email, uid); setDetails(request, authRequest); return this.getAuthenticationManager().authenticate(authRequest); }
Above code works just fine, token is authenticated, OtpProvider, which creates UserDetails from token is also called.
Now according to https://docs.spring.io/spring-security/reference/5.8/migration/servlet/session-management.html#_require_explicit_saving_of_securitycontextrepository in Spring Security 6 context must be saved in securityContextRepository. This is the change from Spring Security 5 that may explain my case but my otp filter which extends AbstractAuthenticationProcessingFilter
should have this handled. Method AbstractAuthenticationProcessingFilter#successfulAuthentication
which creates authenticated SecurityContext and saves the context in securityContextRepository is called.
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { SecurityContext context = this.securityContextHolderStrategy.createEmptyContext(); context.setAuthentication(authResult); this.securityContextHolderStrategy.setContext(context); this.securityContextRepository.saveContext(context, request, response);
Finally i managed to make this work disabling securityContext explicit save. But it seems this is not a valid solution.
.securityContext((securityContext) -> securityContext.requireExplicitSave(false))
How to make this work without disabling explicit save?