Search code examples
spring-bootspring-security

Confirm that custom filters (using @Order and @Component) are running after Spring Security filters in Spring Boot 3


I'm building a Spring Boot v3.1.1 application using Spring Security as a dependency.

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.1.1'
    id 'io.spring.dependency-management' version '1.1.0'
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-security'
    implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server'
}

I have created two custom filters expecting that they run after (last in the chain) Spring Security filters:

@Order(1)
@Component
public class DeviceFilter extends OncePerRequestFilter {}

@Order(2)
@Component
public class MdcFilter extends OncePerRequestFilter {}

They are working, but I cannot confirm that the order is correct.

I have tried this:

@Configuration
@EnableWebSecurity(debug = true)
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .formLogin(AbstractHttpConfigurer::disable)
                .httpBasic(AbstractHttpConfigurer::disable)
                .csrf(AbstractHttpConfigurer::disable) // Removes CsrfFilter
                .logout(AbstractHttpConfigurer::disable) // Removes LogoutFilter
                .rememberMe(AbstractHttpConfigurer::disable) // Removes RememberMeAuthenticationFilter
                .requestCache(RequestCacheConfigurer::disable) // Removes RequestCacheAwareFilter
                
                .cors(withDefaults()) // Adds CorsFilter

                .authorizeHttpRequests(auth -> auth
                        .requestMatchers("/public/**").permitAll()
                        .anyRequest().authenticated()
                )

                .sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .oauth2ResourceServer(oauth2 -> oauth2.jwt(withDefaults()));

        return http.build();
    }

}

Result in the console:

Security filter chain: [
  DisableEncodeUrlFilter
  WebAsyncManagerIntegrationFilter
  SecurityContextHolderFilter
  HeaderWriterFilter
  CorsFilter
  BearerTokenAuthenticationFilter
  SecurityContextHolderAwareRequestFilter
  AnonymousAuthenticationFilter
  SessionManagementFilter
  ExceptionTranslationFilter
  AuthorizationFilter
]

If you can notice, my two custom filters are not there, but they are working somehow.

I think that Spring Security filters are independent of custom filters, and these are not added to the array of filters that they manage. I'm not sure, I'm not an expert on the subject.

Also, I have tried to put logs inside of my two custom filters to check the order. They are good.

09:05AM INFO DeviceFilter                     : {} DeviceFilter!!!!!!!
09:05AM INFO MdcFilter                        : {} MdcFilter!!!!!!!

But so far, I cannot guarantee that they are running after Spring Security filters. The reason is that I only want them to run if the user is successfully authenticated and has passed all the Spring Security checks.

Another thing that I have tried is to add my filters inside the securityFilterChain() method, this implies that my filters no longer implement @Order neither @Component:

@Configuration
@EnableWebSecurity(debug = true)
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                // .......
                // ......
                

                .addFilterAfter(new DeviceFilter(), AuthorizationFilter.class);
                .addFilterAfter(new MdcFilter(), DeviceFilter.class);

        return http.build();
    }

}

Result in the console:

Security filter chain: [
  DisableEncodeUrlFilter
  WebAsyncManagerIntegrationFilter
  SecurityContextHolderFilter
  HeaderWriterFilter
  CorsFilter
  BearerTokenAuthenticationFilter
  SecurityContextHolderAwareRequestFilter
  AnonymousAuthenticationFilter
  SessionManagementFilter
  ExceptionTranslationFilter
  AuthorizationFilter
  DeviceFilter // <=================
  MdcFilter // <=================
]

I would prefer to stick with the @Order and @Component approach because it's easier for me.

In other threads, they talk about how to set the order.

My question is how to confirm that the order I have (using @Order and @Component) is correct and they are running after Spring Security filters?

Thanks in advance and any push on this is appreciated.


Solution

  • Your guess is correct . The filter used by the spring security (i.e FilterChainProxy) internally has its own filter chain (i.e SecurityFilterChain) which the order of those internal Filters will not be affected by @Order. The @Order only has effect on the standard servlet Filter beans including FilterChainProxy.

    By analysing the source codes , ServletContextInitializerBeans is responsible for sorting all the standard servlet Filter beans based on @Order. Then after that , it will register the Filter to the servlet container one by one based on this sorting order.

    So you can use it to indirectly confirm the Filter orders by defining a CommandLineRunner which will execute during application startup to print out the order. Something like :

    @Bean
    public CommandLineRunner cmdLineRunner(ApplicationContext context) {
        return args -> {
            ServletContextInitializerBeans scib = new ServletContextInitializerBeans(context,
                    FilterRegistrationBean.class, DelegatingFilterProxyRegistrationBean.class);
            System.out.println("----");
            scib.iterator().forEachRemaining(s -> {
                System.out.println(s);
            });
        };
    }
    

    And it will print out something like :

    characterEncodingFilter urls=[/*] order=-2147483648
    formContentFilter urls=[/*] order=-9900
    requestContextFilter urls=[/*] order=-105
    springSecurityFilterChain urls=[/*] order=-100
    deviceFilter urls=[/*] order=1
    mdcFilter urls=[/*] order=2
    filterRegistrationBean urls=[/*] order=2147483647
    dispatcherServlet urls=[/]