首页 > 解决方案 > 禁用 WebFilter 的自动注册

问题描述

在 spring-boot 2.4 应用程序中,我有两个SecurityWebFilterChains. 对于其中一个,我想通过addFilterBefore()WebFilter添加一些s 。

@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()
}

但是,由于这些WebFilters 是作为 bean 创建的,它们会自动注册并应用于所有请求,似乎在安全链之外。

对于 servlet 过滤器,可以使用 a 禁用此注册FilterRegistrationBean(请参阅spring-boot 文档)。

reactive 是否有类似WebFilter的方法,或者我是否必须在这些过滤器中添加额外的 URL 过滤?

标签: springspring-bootkotlinspring-securityspring-webflux

解决方案


要找到解决方案,我们首先必须更深入地研究弹簧的工作原理及其内部结构。

所有类型的 bean 都会WebFilter自动添加到主 Web 处理过滤器链中。

请参阅有关该主题的spring boot 文档

在应用程序上下文中找到的 WebFilter bean 将自动用于过滤每个交换。

即使您希望它们仅应用于特定的弹簧安全过滤器链,也会发生这种情况。

(恕我直言,重用FilterorWebFilter接口并且没有具有相同签名的特定于安全性的东西是spring-security的一个缺陷。)

在代码中,相关部分在spring-web的WebHttpHandlerBuilder中

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

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

    // ...
}

这又在 spring-boot 的HttpHandlerAutoConfiguration中调用以创建主 HttpHandler。

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

为了防止将这些过滤器应用于所有交换,可以简单地不将它们创建为 bean 并手动创建它们,如上面的评论中所建议的那样。那么 BeanProvider 将不会找到它们并且不会将它们添加到 HttpHandler 中。但是,您离开 IoC 国家并失去过滤器的自动配置。当这些过滤器变得更复杂或当你有很多过滤器时,这并不理想。

因此,我的解决方案是为我的应用程序手动配置一个HttpHandler,它不会将我的安全特定过滤器添加到全局过滤器链中。

为了完成这项工作,我首先为我的过滤器声明了一个标记接口。

interface NonGlobalFilter

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

然后,在创建自定义 HttpHandler 的地方需要一个配置类。方便的是,WebHttpHandlerBuilder 有一个使用 Consumer操作其过滤器列表的方法。

这将阻止 spring-boot 从HttpHandlerAutoConfiguration使用它自己的 HttpHandler,因为它是用@ConditionalOnMissingBean(HttpHandler.class).

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

就是这样!与往常一样,spring 提供了许多开箱即用的有用默认值,但是当它妨碍您时,将有一种方法可以根据需要对其进行调整。


推荐阅读