Home > other >  Migrating from WebSecurityConfigurerAdapter to SecurityFilterChain
Migrating from WebSecurityConfigurerAdapter to SecurityFilterChain

Time:07-28

Here is my working security conf before migration :


    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring()
                .antMatchers("/auth/**")
                .antMatchers("/swagger-ui/**")
                .antMatchers("/swagger-ui.html")
                .antMatchers("/swagger-resources/**")
                .antMatchers("/v2/api-docs/**")
                .antMatchers("/v3/api-docs/**");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
        jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedPortalRoleConverter);

        http
                .csrf().disable()
                .cors()
                .and()
                .exceptionHandling()
                .authenticationEntryPoint(new AuthenticationFallbackEntryPoint())
                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests(authorize -> authorize.anyRequest().authenticated())
                .oauth2ResourceServer()
                .jwt().jwtAuthenticationConverter(jwtAuthenticationConverter);
    }

And here is my Security chain config after migration :

    @Bean
    @Order(1)
    public SecurityFilterChain ignorePathsSecurityFilterChain(HttpSecurity http) throws Exception {
        http
                .authorizeHttpRequests(authorize -> authorize
                        .antMatchers(
                                "/auth/**",
                                "/swagger-ui/**",
                                "/swagger-ui.html",
                                "/swagger-resources/**",
                                "/v3/api-docs/**")
                            .permitAll());

        return http.build();
    }

    @Bean
    @Order(2)   
    public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http, GrantedPortalRoleConverter grantedPortalRoleConverter) throws Exception {
        JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
        jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedPortalRoleConverter);

        http
                .csrf().disable()
                .cors(Customizer.withDefaults())
                .exceptionHandling(configurer -> configurer.authenticationEntryPoint(new AuthenticationFallbackEntryPoint()))
                .sessionManagement(configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated())
                .oauth2ResourceServer(configurer -> configurer.jwt().jwtAuthenticationConverter(jwtAuthenticationConverter));

        return http.build();
    }

With the original conf, when I call a random non existing path :

    @Test
    void should_not_authenticate_or_return_not_found() throws Exception {
        logger.info("should_not_authenticate_or_return_not_found");
        
        mvc.perform(get("/toto/tata"))
                .andExpect(status().isUnauthorized());      
    }

I get :

15:44:00.230 [main] DEBUG o.s.s.w.a.i.FilterSecurityInterceptor - Failed to authorize filter invocation [GET /toto/tata] with attributes [authenticated]

With the new conf, I'm just getting HTTP 404, what am I missing here please ? I can't see any difference and debug logs don't show much.

Here is the first line of log missing using the non working conf :

16:24:58.651 [main] DEBUG o.s.s.w.a.e.ExpressionBasedFilterInvocationSecurityMetadataSource - Adding web access control expression [authenticated] for any request

But in both logs, I can see (2 lines of this for the new conf since there are 2 security chains) :

o.s.s.web.DefaultSecurityFilterChain - Will secure any request with (...)

CodePudding user response:

Explanation

When you have multiple SecurityFilterChains, you have to specify a request matcher, otherwise all requests will be processed by the first SecurityFilterChain, annotated with @Order(1), and never reach the second SecurityFilterChain, annotated with @Order(2).

In the code you shared above, this means configuring .requestMatchers() in ignorePathsSecurityFilterChain:

@Bean
@Order(1)
public SecurityFilterChain ignorePathsSecurityFilterChain(HttpSecurity http) throws Exception {
    http
        .requestMatchers(requests -> requests // add this block
            .antMatchers(
                "/auth/**",
                "/swagger-ui/**",
                "/swagger-ui.html",
                "/swagger-resources/**",
                "/v3/api-docs/**")
        )
        .authorizeHttpRequests(authorize -> authorize
            .antMatchers(
                "/auth/**",
                "/swagger-ui/**",
                "/swagger-ui.html",
                "/swagger-resources/**",
                "/v3/api-docs/**")
            .permitAll());

    return http.build();
}

This means that only the requests matching /auth/**, /swagger-ui/** etc will be processed by ignorePathsSecurityFilterChain, while the rest of the requests will move on to defaultSecurityFilterChain.

To understand the difference between requestMatchers and authorizeHttpRequests you can check out this StackOverflow question.

Solution

An even better option is to combine the SecurityFilterChains into a single one. I don't see any reason why you would separate them in this case.

The resulting configuration would be:

@Bean
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http, GrantedPortalRoleConverter grantedPortalRoleConverter) throws Exception {
    JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
    jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedPortalRoleConverter);

    http
            .authorizeHttpRequests(authorize -> authorize
                    .antMatchers(
                            "/auth/**",
                            "/swagger-ui/**",
                            "/swagger-ui.html",
                            "/swagger-resources/**",
                            "/v3/api-docs/**")
                    .permitAll()
                    .anyRequest().authenticated()
            )
            .csrf().disable()
            .cors(Customizer.withDefaults())
            .exceptionHandling(configurer -> configurer.authenticationEntryPoint(new AuthenticationFallbackEntryPoint()))
            .sessionManagement(configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .oauth2ResourceServer(configurer -> configurer.jwt().jwtAuthenticationConverter(jwtAuthenticationConverter));

    return http.build();
}

Alternative

Alternatively you can use a WebSecurityCustomizer to ignore certain endpoints:

@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
    return (web) -> web.ignoring().antMatchers(
                "/auth/**",
                "/swagger-ui/**",
                "/swagger-ui.html",
                "/swagger-resources/**",
                "/v3/api-docs/**");
}

Then you would use defaultSecurityFilterChain as your only SecurityFilterChain.

  • Related