Отключить автоматическую регистрацию веб-фильтра

#spring #spring-boot #kotlin #spring-security #spring-webflux

Вопрос:

В приложении spring-boot 2.4 у меня есть два SecurityWebFilterChain s. Только для одного из них я хочу добавить несколько WebFilter s через 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()
}

 

Однако, поскольку эти WebFilter s создаются как компоненты, они регистрируются автоматически и применяются ко всем запросам, по-видимому, вне цепочки безопасности.

Для фильтров сервлетов можно отключить эту регистрацию с помощью a FilterRegistrationBean (см. Документацию по весенней загрузке).

Существует ли аналогичный способ для реактивных WebFilter s, или мне нужно добавить дополнительную фильтрацию URL-адресов в эти фильтры?

Комментарии:

1. Почему бы просто не создать их в виде бобов?

2. Эти фильтры зависят от других компонентов, для краткости не показанных в примере.

3. Затем автоматически подключайте эти компоненты и загружайте их в конструктор фильтра во время создания экземпляров.

4. Звучит выполнимо, но также немного противоречит принципам инверсии контроля.

5. ну, вы уже игнорируете принципы IoC, вручную создав экземпляр и установив фильтр… ооооо…… вы начали это, а не я 🙂

Ответ №1:

Чтобы найти решение, мы сначала должны немного глубже разобраться в том, как работает spring и его внутренние компоненты.

Все компоненты типа WebFilter автоматически добавляются в основную цепочку фильтров веб-обработки.

См. Документацию по весенней загрузке по этой теме:

Компоненты веб-фильтра, найденные в контексте приложения, будут автоматически использоваться для фильтрации каждого обмена.

Так что это происходит, даже если вы хотите, чтобы они применялись только к определенной цепочке фильтров защиты пружин.

(ИМХО, это немного недостаток spring-безопасности, чтобы повторно использовать интерфейсы Filter or WebFilter и не иметь чего-то специфичного для безопасности с той же подписью.)

В коде соответствующая часть находится в сборщике WebHttpHandlerBuilder spring-web

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

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

    // ...
}
 

Который, в свою очередь, вызывается в конфигурации HttpHandlerAutoConfiguration spring-boot для создания основного HttpHandler.

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

Чтобы эти фильтры не применялись ко всем биржам, возможно, можно просто не создавать их как компоненты, а создавать вручную, как предложено в комментарии выше. Тогда поставщик BeanProvider не найдет их и не добавит в HttpHandler. Однако вы покидаете страну IoC и теряете автоконфигурацию фильтров. Не идеально, когда эти фильтры становятся более сложными или когда их у вас много.

Поэтому вместо этого мое решение состоит в том, чтобы вручную настроить a HttpHandler для моего приложения, которое не добавляет мои фильтры, относящиеся к безопасности, в глобальную цепочку фильтров.

Чтобы это сработало, я сначала объявляю интерфейс маркера для своих фильтров.

 interface NonGlobalFilter

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

Затем требуется класс конфигурации, в котором создается пользовательский HttpHandler. Удобно, что WebHttpHandlerBuilder имеет метод для управления списком фильтров с потребителем.

Это помешает spring-boot использовать свой собственный HttpHandler из конфигурации HttpHandlerAutoConfiguration, поскольку он снабжен @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 предоставляет множество полезных настроек по умолчанию из коробки, но когда это встанет у вас на пути, будет возможность настроить его по мере необходимости.