Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Disable automatic registration of WebFilter

In a spring-boot 2.4 application, I have two SecurityWebFilterChains. For only one of them, I want to add some WebFilters via addFilterBefore().

@Configuration
@EnableWebFluxSecurity
class WebSecurityConfig {

    @Bean
    fun filter1(service: Service): WebFilter = Filter1(service)

    @Bean
    fun filter2(component: Component): WebFilter = Filter2(component)

    @Bean
    @Order(1)
    fun apiSecurityConfiguration(
        http: ServerHttpSecurity,
        filter1: WebFilter,
        filter2: WebFilter
    ): SecurityWebFilterChain = http
        .securityMatcher(pathMatchers("/path/**"))
        .addFilterBefore(filter1, SecurityWebFiltersOrder.AUTHENTICATION)
        .addFilterAt(filter2, SecurityWebFiltersOrder.AUTHENTICATION)
        .build()

    @Bean
    @Order(2)
    fun actuatorSecurityConfiguration(
        http: ServerHttpSecurity,
        reactiveAuthenticationManager: ReactiveAuthenticationManager
    ): SecurityWebFilterChain = http
        .securityMatcher(pathMatchers("/manage/**"))
        .authenticationManager(reactiveAuthenticationManager)
        .httpBasic { }
        .build()
}

However, as those WebFilters are created as beans, they are registered automatically and are applied to all requests, seemingly outside the security chain.

For servlet filters, it is possible to disable this registration with a FilterRegistrationBean (see spring-boot documentation).

Is there a similar way for reactive WebFilters, or do I have to add additional URL filtering into those filters?

like image 541
Rüdiger Schulz Avatar asked Sep 19 '25 06:09

Rüdiger Schulz


1 Answers

To find a solution, we first have to dig in a little deeper into how spring works and its internals.

All beans of type WebFilter are automatically added to the main web handling filter chain.

See spring boot documentation on the topic:

WebFilter beans found in the application context will be automatically used to filter each exchange.

So that happens even if you want them to be applied only to a specific spring-security filter chain.

(IMHO, it is a bit of a flaw of spring-security to re-use the Filter or WebFilter interfaces and not have something security-specific with the same signature.)

In code, the relevant part is in spring-web's WebHttpHandlerBuilder

public static WebHttpHandlerBuilder applicationContext(ApplicationContext context) {
    // ...

    List<WebFilter> webFilters = context
            .getBeanProvider(WebFilter.class)
            .orderedStream()
            .collect(Collectors.toList());
    builder.filters(filters -> filters.addAll(webFilters));

    // ...
}

Which in turn is called in a spring-boot's HttpHandlerAutoConfiguration to create the main HttpHandler.

@Bean
public HttpHandler httpHandler(ObjectProvider<WebFluxProperties> propsProvider) {
    HttpHandler httpHandler = WebHttpHandlerBuilder.applicationContext(this.applicationContext).build();
    // ...
    return httpHandler;
}

To prevent those filters to be applied to all exchanges, it might be possible to simply not create them as beans and create them manually, as suggested in a comment above. Then the BeanProvider will not find them and not add them to the HttpHandler. However, you leave IoC-country and lose autoconfiguration for the filters. Not ideal when those filters become more complex or when you have a lot of them.

So instead my solution is to manually configure a HttpHandler for my application, which does not add my security-specific filters to the global filter chain.

To make this work, I first declare a marker interface for my filters.

interface NonGlobalFilter

class MySecurityFilter : WebFilter, NonGlobalFilter {
  // ...
}

Then, a configuration class is required where the custom HttpHandler is created. Conveniently, WebHttpHandlerBuilder has a method to manipulate its filter list with a Consumer.

This will prevent spring-boot to use its own HttpHandler from HttpHandlerAutoConfiguration because it is annotated with @ConditionalOnMissingBean(HttpHandler.class).

@Configuration
class WebHttpHandlerConfiguration(
    private val applicationContext: ApplicationContext
) {
    @Bean
    fun httpHandler() = WebHttpHandlerBuilder
        .applicationContext(applicationContext)
        .filters {
            it.removeIf {
                webFilter -> webFilter is NonGlobalFilter
            }
        }
        .build()
}

And that's it! As always, spring provides a lot of useful defaults out of the box, but when it gets in your way, there will be a means to adjust it as necessary.

like image 147
Rüdiger Schulz Avatar answered Sep 21 '25 00:09

Rüdiger Schulz