In a spring-boot 2.4 application, I have two SecurityWebFilterChain
s. For only one of them, I want to add some WebFilter
s 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 WebFilter
s 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 WebFilter
s, or do I have to add additional URL filtering into those filters?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With