Изменение порядка Spring Security WebFilter

Изменение порядка Spring Security WebFilter

У меня есть API-шлюз, реализованный с использованием Spring Cloud Gateway, который использует Spring Security. Spring Security для WebFlux реализован как WebFilter в самом начале цепочки фильтров. Таким образом, после успешной аутентификации запрос будет перенаправлен на RoutePredicateHandlerMapping Spring Cloud Gateway, который попытается определить пункт назначения на основе шаблона URL, а затем он перейдет в FilteringWebHandler для выполнения других фильтров Spring Cloud Gateway.

Моя проблема заключается в следующем: я реализовал индивидуальный алгоритм аутентификации, который использует строку запроса и переменные заголовка в качестве учетных данных для аутентификации в соответствии с требованиями проекта, и это работает без каких-либо проблем. Проблема возникла, когда нам нужно было добавить небольшую настройку для алгоритма аутентификации, независимого от пути. Когда запрос достигает WebFilter Spring Security, сопоставление с образцом еще не выполнено, поэтому я не знаю, на какое приложение он указывает, например:

приложение1:

-Путь: / app1 / **

приложение2:

-Путь: / app2 / **

Это означает, что вместо проверки подлинности -> сопоставления маршрутов -> веб-обработчика фильтрации я должен выполнить сопоставление маршрутов -> проверка подлинности -> веб-обработчик фильтрации. Не то чтобы эти три компонента не похожи друг на друга, один из них - фильтр, другой - преобразователь, а последний - веб-обработчик. Теперь я знаю, как их настраивать, но проблема в том, что я не знаю, как перехватить процесс сборки сервера Netty, чтобы изменить порядок этих операций. Мне нужно дождаться завершения процесса сборки и изменить содержимое сервера перед его запуском. Как я могу это сделать?


person Sam    schedule 04.10.2018    source источник


Ответы (3)


РЕДАКТИРОВАТЬ: вот окончательное решение: вот как я это сделал:

Цель: удалить WebFilter Spring Security из HttpHandler по умолчанию и вставить его между RoutePredicateRouteMapping и FilteringWebHandler из Spring Cloud Gateway.

Зачем: потому что мне нужно знать идентификатор приложения при выполнении индивидуального процесса аутентификации. Этот идентификатор приложения прикрепляется к запросу с помощью RoutePredicateRouteMapping путем сопоставления URL-адреса запроса с предопределенным списком.

Как я это сделал: 1- Удаление WebFilter Spring Security Я создал bean-компонент HttpHandler, который вызывает WebHttpHandlerBuilder по умолчанию, а затем настраивает фильтры. В качестве бонуса я удалил ненужные фильтры, чтобы повысить производительность моего шлюза API.

@Bean
public HttpHandler httpHandler() {
    WebHttpHandlerBuilder webHttpHandlerBuilder = WebHttpHandlerBuilder.applicationContext(this.applicationContext);

    MyAuthenticationHandlerAdapter myAuthenticationHandlerAdapter = this.applicationContext.getBean(MY_AUTHENTICATED_HANDLER_BEAN_NAME, MyAuthenticationHandlerAdapter.class);

    webHttpHandlerBuilder
            .filters(filters ->
                    myAuthenticationHandlerAdapter.setSecurityFilter(
                            Collections.singletonList(filters.stream().filter(f -> f instanceof WebFilterChainProxy).map(f -> (WebFilterChainProxy) f).findFirst().orElse(null))
                    )
            );

    return webHttpHandlerBuilder.filters(filters -> filters
            .removeIf(f -> f instanceof WebFilterChainProxy || f instanceof WeightCalculatorWebFilter || f instanceof OrderedHiddenHttpMethodFilter))
            .build();
}

2- Обертывание FilteringWebHandler Spring Cloud Gateway с помощью Spring Web FilteringWebHandler с добавленным WebFilter Я создал свой собственный HandlerAdapter, который соответствовал бы FilteringWebHandler Spring Cloud Gateway, и обернул его с помощью Spring Web FilteringWebHandler плюс фильтр безопасности, который я извлек на первом этапе.

@Bean
public MyAuthenticationHandlerAdapter myAuthenticationHandlerAdapter() {
    return new MyAuthenticationHandlerAdapter();
}

public class MyAuthenticationHandlerAdapter implements HandlerAdapter {
    @Setter
    private List<WebFilter> securityFilter = new ArrayList<>();


    @Override
    public boolean supports(Object handler) {
        return handler instanceof FilteringWebHandler;
    }

    @Override
    public Mono<HandlerResult> handle(ServerWebExchange exchange, Object handler) {
        org.springframework.web.server.handler.FilteringWebHandler filteringWebHandler = new org.springframework.web.server.handler.FilteringWebHandler((WebHandler) handler, securityFilter);
        Mono<Void> mono = filteringWebHandler.handle(exchange);
        return mono.then(Mono.empty());
    }
}

Таким образом, я мог добиться лучшей производительности с помощью настраиваемого конвейера HttpHandler, который, как я полагаю, будет ориентирован на будущее.

КОНЕЦ РЕДАКТИРОВАНИЯ

Spring Security для WebFlux реализован как WebFilter, который запускается почти сразу после получения запроса. Я реализовал собственный конвертер аутентификации и менеджер аутентификации, который извлекает некоторые переменные из заголовка и URL-адреса и использует их для аутентификации. Это работает без проблем.

Теперь мне нужно было добавить еще одну переменную, взятую из RoutePredicateRouteMapping, прежде чем аутентификация будет выполнена. Я хочу именно удалить WebFilter (называемый WebFilterChainProxy) из его текущей позиции и поместить его между RoutePredicateRouteMapping и FilteringWeHandler.

Вот как проходит процесс по умолчанию:

ChannelOperations вызывает ReactorHttpHandlerAdapter, который вызывает HttpWebHandlerAdapter, ExceptionHandlingWebHandler, а затем org.springframework.web.server.handler.FilterWebHandler.

Этот WebHandler вызовет свои фильтры, а затем вызовет DispatchHandler. Одним из таких фильтров является WebFilterChainProxy, который выполняет аутентификацию для Spring Security. Итак, первым делом нужно удалить отсюда фильтр.

Теперь DispatchHandler, который вызывается после фильтров, будет вызывать RoutePredicateHandlerMapping, который будет анализировать маршруты и дать мне идентификатор маршрута, который мне нужен, а затем вызовет org.springframework.cloud.gateway.handler.FilteringHandler (это не тот же FilteringHandler выше), и это, в свою очередь, вызовет другие фильтры Spring Cloud Gateway. Я хочу вызвать фильтр после RoutePredicatehandlerMapping и до org.springframework.cloud.gateway.handler.FilteringHandler. В конце концов я сделал следующее:

Я создал и WebHttpHandlerBuilder, который удалит WebFilterChainProxy и передаст его в качестве параметра настраиваемому DispatcherHandler. Теперь, когда фильтр удален, запрос пройдет первые уровни без необходимости аутентификации. В моем настроенном DispatcherHandler я бы вызвал RoutePredicateHandlerMapping, а затем передал бы переменную обмена в WebFilterChainProxy, чтобы выполнить аутентификацию, прежде чем передавать ее в org.springframework.cloud.gateway.handler.FilteringHandler, который работал отлично! Я все еще думаю, что переборщил с разработкой, и надеюсь, что есть способ сделать это, используя аннотации и компоненты конфигурации вместо всех этих настраиваемых классов (WebHttpHandlerBuilder и DispatcherHandler).

person Sam    schedule 05.10.2018

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

Кроме того, не размещайте перекрестные сообщения, команда Spring активно отслеживает Переполнение стека.

person Brian Clozel    schedule 04.10.2018
comment
Фильтр безопасности не мой, это веб-фильтр, входящий в состав Spring Security. Считаете ли вы, что было бы неплохо разрешить запросы All (), а затем создать GlobalFilter с наивысшим порядком (выполняется перед всеми другими фильтрами), а затем вызвать фильтр Spring Security вручную, чтобы выполнить аутентификацию и (большинство главное) заполните данные аутентификации? Или это создаст проблемы для будущих реализаций Spring Security? - person Sam; 05.10.2018
comment
Я тщательно подумал о реализации его как GlobalFilter, но это не поможет по двум причинам: 1- Мне нужен способ удалить прокси-сервер фильтрации по умолчанию из его позиции 2- Для вызова прокси-сервера аутентификации из GlobalFilter требуются обработчики, которые недоступны мне в GlobalFilter. См. мой ответ выше. Я думаю, что в будущем решение будет доработано, но в основном оно работает так, как я его описал. - person Sam; 05.10.2018

У меня была похожая проблема. Принятое решение, хотя и было интересным, было для меня немного радикальным. Мне удалось заставить его работать, просто добавив свой собственный фильтр перед SecurityWebFiltersOrder.AUTHENTICATION в конфигурации безопасности. Это похоже на то, что я успешно делал в обычном приложении Spring mvc.

Вот пример использования аутентификации oauth. tokenIntrospector - это мой настраиваемый интроспектор, а requestInitializationFilter - это фильтр, который захватывает идентификатор клиента и прячет его в контексте.

@AllArgsConstructor
@Configuration
@EnableWebFluxSecurity
public class WebApiGatewaySecurityConfiguration {

    private final GatewayTokenIntrospector tokenIntrospector;

    private final GatewayRequestInitializationFilter requestInitializationFilter;

    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
        // @formatter:off
            http
                .formLogin().disable()
                .csrf().disable()

                .oauth2ResourceServer(oauth2ResourceServer ->
                    oauth2ResourceServer.opaqueToken(c -> c.introspector(tokenIntrospector)))

                .addFilterBefore(requestInitializationFilter, SecurityWebFiltersOrder.AUTHENTICATION);

            return http.build();
            // @formatter:on
    }
}
person rougou    schedule 30.04.2020