springspring-bootspring-securityspring-boot-test

Why are there 2 Security Filter Chains defined for my Spring Boot Application?


I am trying to add a filter chain into my spring boot application like below.

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http, MyTokenIntrospector myTokenIntrospector) throws Exception {
        return http
                .httpBasic(AbstractHttpConfigurer::disable)
                .formLogin(AbstractHttpConfigurer::disable)
                .authorizeHttpRequests(
                        authorizeHttpRequests -> authorizeHttpRequests
                                .requestMatchers("/actuator/health").permitAll()
                                .dispatcherTypeMatchers(DispatcherType.ERROR).permitAll()
                                .requestMatchers(HttpMethod.OPTIONS).permitAll() //for csrf OPTIONS queries
                                .anyRequest().authenticated()
                )
                .csrf(AbstractHttpConfigurer::disable)
                .addFilterAfter(new MyAuthExtractorFilter(), BearerTokenAuthenticationFilter.class)
                .oauth2ResourceServer(oauth2 -> oauth2
                        .bearerTokenResolver(new MyBasicTokenResolver())
                        .opaqueToken(opaqueToken -> opaqueToken.introspector(myTokenIntrospector))
                        .authenticationEntryPoint(new BasicAuthenticationEntryPoint())
                )
                .sessionManagement(sessionManager -> sessionManager.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .build();
    }

I noticed that spring boot was invoking a different filter chain than the one that I defined above. I could always put @Order(1) on the bean, but I would like to know why there is another filter chain defined.

When I put a breakpoint in org.springframework.security.WebSecurityConfiguration in the block below, I can see that there are 2 filter chains defined in this.securityFilterChains. One of them is the one that I defined, and the other one seems to be some kind of default.

Is there a way to tell spring to just use my filter chain that I defined? Or do I need to apply Order(1) to use mine?

   public Filter springSecurityFilterChain() throws Exception {
        boolean hasFilterChain = !this.securityFilterChains.isEmpty();
        if (!hasFilterChain) {
            this.webSecurity.addSecurityFilterChainBuilder(() -> {
                this.httpSecurity.authorizeHttpRequests((authorize) -> {
                    ((AuthorizeHttpRequestsConfigurer.AuthorizedUrl)authorize.anyRequest()).authenticated();
                });
                this.httpSecurity.formLogin(Customizer.withDefaults());
                this.httpSecurity.httpBasic(Customizer.withDefaults());
                return (SecurityFilterChain)this.httpSecurity.build();
            });
        }

UPDATE

For some context, I am building a spring boot security library for use in all of my spring applications. Here is the full security configuration class that I am using.

@Configuration
@ComponentScan(basePackages = {"org.app.security"}, excludeFilters = {@ComponentScan.Filter(Configuration.class)})
@EnableWebSecurity
public class MySecurityConfiguration {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http, MtServerTokenIntrospector mtserverTokenIntrospector) throws Exception {
        return http
                .httpBasic(AbstractHttpConfigurer::disable)
                .formLogin(AbstractHttpConfigurer::disable)
                .authorizeHttpRequests(
                        authorizeHttpRequests -> authorizeHttpRequests
                                .requestMatchers("/actuator/health").permitAll()
                                .dispatcherTypeMatchers(DispatcherType.ERROR).permitAll()
                                .requestMatchers(HttpMethod.OPTIONS).permitAll() //for csrf OPTIONS queries
                                .anyRequest().authenticated()
                )
                .csrf(AbstractHttpConfigurer::disable)
                .addFilterAfter(new MyAuthExtractorFilter(), BearerTokenAuthenticationFilter.class)
                .oauth2ResourceServer(oauth2 -> oauth2
                        .bearerTokenResolver(new MyBasicTokenResolver())
                        .opaqueToken(opaqueToken -> opaqueToken.introspector(mtserverTokenIntrospector))
                        .authenticationEntryPoint(new BasicAuthenticationEntryPoint())
                )
                .sessionManagement(sessionManager -> sessionManager.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .build();
    }
}

I am also using this auto-configuration that I've setup.

@Configuration
@AutoConfigureBefore(SecurityAutoConfiguration.class)
@AutoConfigureAfter(MyHttpClientAutoConfiguration.class)
@ConditionalOnProperty(prefix = "app.security", name = "enabled", havingValue = "true", matchIfMissing = true)
@ComponentScan(basePackages = "org.app.security.config")
public class MySecurityAutoConfiguration {
}

Solution

  • I figured out that by using OAuth2 Resource Server, we are invoking oauth2 auto configuration through OAuth2ResourceServerAutoConfiguration.class. As a result, it will create a default filter chain if one isn't detected.

    Instead of executing before the SecurityAutoConfiguration class, I decided to execute before the OAuth2ResourceServerAutoConfiguration class instead, and it works like a charm. I am only getting a single security filter chain.