Skip to content

Spring Security returns 403 instead of 401 and creates invalid Redis session cookie

I’m using Spring Security and Spring Data Redis to keep track of user sessions with custom roles and entitlements. When I try to hit a PreAuthorized endpoint without a session cookie in my browser, it should return a 401. Instead a new (invalid) session cookie is created and the endpoint returns a 403.

Here’s my SecurityConfig:

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, order = Ordered.HIGHEST_PRECEDENCE)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests((authorize) -> authorize
                        .requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
                        .anyRequest().authenticated()
                )

                // SameSite=Strict;
                .csrf().disable().cors();
    }

    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);

        config.addAllowedOrigin("*");

        config.addAllowedMethod(HttpMethod.GET);
        config.addAllowedMethod(HttpMethod.POST);
        config.addAllowedMethod(HttpMethod.DELETE);
        config.addAllowedMethod(HttpMethod.OPTIONS);
        config.addAllowedHeader("Authorization");
        config.addAllowedHeader("Content-Type");
        config.addAllowedHeader("*");
        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);
    }
}

I’m also using MethodSecurityConfig and an implementation of UserDetails to parse the custom fields from the user authentication.

Answer

Here’s the fix, for anyone who encounters a similar issue down the line:

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER).and()   //let redis handle session creation
                .csrf().disable().cors().and()
                .requestCache().disable().exceptionHandling().and()                         //prevent exception creating duplicate session
                .authorizeRequests().anyRequest().authenticated().and()                     //all endpoints need auth
                .exceptionHandling().authenticationEntryPoint(
                        new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED));                 //return 401 on no session
    }