2

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?

Rüdiger Schulz
  • 2,588
  • 3
  • 27
  • 43
  • Why just not create them as beans? – Toerktumlare Mar 19 '21 at 13:35
  • Those filters have dependencies on other beans, not shown in the example for brevity. – Rüdiger Schulz Mar 19 '21 at 13:56
  • Then autowire in those beans and feed them into the constructor of the filter during instansiation. – Toerktumlare Mar 19 '21 at 14:30
  • Sounds doable, but also a bit contradicting to the principles of inversion of control. – Rüdiger Schulz Mar 19 '21 at 15:00
  • well you are already ignoring the principals of IoC by manually instansiating and setting the filter... sooooo...... you started it, not me :) – Toerktumlare Mar 19 '21 at 15:58
  • but im also going to ask, what do the filters do? – Toerktumlare Mar 19 '21 at 16:00
  • Not sure why the purpose of the filters would help with answering the question. Which aims at spring-security configuration. I have a similar configuration for servlets and it seems I have more flexibility there regarding where filter beans are being auto-injected. Also not sure why using the builder pattern of spring security configuration is violating IoC. – Rüdiger Schulz Mar 19 '21 at 16:47
  • `Not sure why the purpose of the filters would help with answering the question` because if the filters are something that already exists in spring security then we might not even need this conversation. You are violating IoC as soon as you call `new` (well this is kotlin no new but you get the point) and not letting the context instansiate and manage the lifecycle of your object. – Toerktumlare Mar 19 '21 at 17:19
  • As this is a Configuration class with a Bean annotated method, lifecycle IS mamaged by Spring. This is an alternative to using Component annotations. My problem is that every Bean of type WebFilter is attached automatically to every request. And the filters are a) logging app-specific headers b) converting a JWT token into a domain-specific security principal. Surely not available out of the box. – Rüdiger Schulz Mar 19 '21 at 17:29
  • Yes your `@configuration` class is managed by spring, but not your filter, because you have a hard dependency between your filter and your config class where you are instantiating the filter, and not the context. I am fully aware of how spring works and its internals. – Toerktumlare Mar 19 '21 at 17:33
  • Spring security 5 has full customizable jwtfilter support. https://docs.spring.io/spring-security/site/docs/current/reference/html5/#oauth2resourceserver-jwt-architecture so no need for a custom JWTfilter. – Toerktumlare Mar 19 '21 at 17:36
  • Please provide alternative code then. I don't see where my Filters have a dependency on the configuration class. They have dependencies on other services. – Rüdiger Schulz Mar 19 '21 at 17:37
  • I know about that JWTFilter and I have additional requirements. Also, this is quite derailing from my question. I want a filter to be used only in once security chain and not everywhere. – Rüdiger Schulz Mar 19 '21 at 17:40

1 Answers1

2

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.

Rüdiger Schulz
  • 2,588
  • 3
  • 27
  • 43