更改 Spring 安全 WebFilter 的顺序

Changing the Order of the Spring Security WebFilter

更改 Spring 安全 WebFilter 的顺序

我有一个 API 网关,使用 Spring 云网关实现,该网关使用 Spring 安全性。 Spring WebFlux 的安全性在过滤器链的开头作为 WebFilter 实现。因此,在成功验证后,请求将被转发到 Spring Cloud Gateway 的 RoutePredicateHandlerMapping,它会尝试根据 URL 模式推断出目的地,然后它会转到 FilteringWebHandler 以执行其他过滤器Spring 云网关。

我的问题如下:我已经实现了一个自定义的身份验证算法,它根据项目的要求使用查询字符串和 header 变量作为身份验证的凭据,这没有任何问题。当我们需要为独立于路径的身份验证算法添加一个小的定制时,问题就出现了。当请求到达Spring Security的WebFilter时,模式匹配还没有完成,所以我不知道它指向哪个应用程序,例如:

app1:

-路径:/app1/**

app2:

-路径:/app2/**

这意味着我应该执行路由映射 -> 身份验证 -> 过滤 Web 处理程序,而不是进行身份验证 -> 路由映射 -> 过滤 Web 处理程序。并不是说这三个组件不相似,其中一个是过滤器,另一个是映射器,最后一个是 Web 处理程序。现在我知道如何自定义它们,但问题是我不知道如何拦截 Netty 服务器构建过程以更改这些操作的顺序。我需要等待构建过程结束并在服务器启动之前更改服务器的内容。我该怎么做?

您可能应该将该安全过滤器实现为适当的 GatewayFilter,因为只有那些才知道其他 GatewayFilter 实例并且可以相应地进行排序。在你的情况下,你可能想在路由之后订购它。

此外,please don't cross-post,Spring 团队正在积极监控 Whosebug。

编辑:这是最终的解决方案: 所以这就是我的做法:

目标:从默认的HttpHandler中移除Spring Security的WebFilter,并将其插入到Spring Cloud Gateway

的RoutePredicateRouteMapping和FilteringWebHandler之间

原因:因为我在进行自定义身份验证过程时需要知道应用程序 ID。 RoutePredicateRouteMapping 通过将请求的 URL 与预定义列表匹配,将此应用程序 ID 附加到请求。

我是怎么做到的: 1- 删除 Spring 安全的 WebFilter 我创建了一个调用默认 WebHttpHandlerBuilder 的 HttpHandler bean,然后自定义过滤器。作为奖励,我删除了不需要的过滤器以提高 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- 包装 Spring Cloud Gateway 的 FilteringWebHandler 和 Spring Web 的 FilteringWebHandler 添加的 WebFilter 我创建了自己的 HandlerAdapter,它将与 Spring Cloud Gateway 的 FilteringWebHandler 相匹配,并用 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 管道实现更好的性能,我认为 future-proof

结束编辑

Spring WebFlux 的安全性是作为 WebFilter 实现的,几乎在收到请求后立即执行。我已经实现了自定义身份验证转换器和身份验证管理器,它们将从 header 和 URL 中提取一些变量并将它们用于身份验证。这没有任何问题。

现在我需要在身份验证完成之前添加另一个从 RoutePredicateRouteMapping 获取的变量。我真正想要的是从当前位置删除 WebFilter(称为 WebFilterChainProxy)并将其放在 RoutePredicateRouteMapping 和 FilteringWeHandler 之间。

默认流程如下:

ChannelOperations 调用 ReactorHttpHandlerAdapter,后者调用 HttpWebHandlerAdapter、ExceptionHandlingWebHandler,然后 org.springframework.web.server.handler.FilterWebHandler。

此 WebHandler 将调用其过滤器,然后调用 DispatchHandler。其中一个过滤器是 WebFilterChainProxy,它为 Spring 安全性进行身份验证。所以第一步是从这里删除过滤器。

现在在过滤器之后调用的 DispatchHandler 会调用 RoutePredicateHandlerMapping,它会分析路由并给我我需要的路由 ID,然后它会调用 org.springframework.cloud.gateway.handler.FilteringHandler(这不一样上面的 FilteringHandler),然后它会调用 Spring 云网关的其他过滤器。我在这里想要的是在 RoutePredicatehandlerMapping 之后和 org.springframework.cloud.gateway.handler.FilteringHandler 之前调用过滤器。 我最后做了以下事情:

我创建了 WebHttpHandlerBuilder,它将删除 WebFilterChainProxy 并将其作为参数传递给自定义的 DispatcherHandler。现在删除了过滤器,请求将通过第一层而不需要身份验证。在我自定义的 DispatcherHandler 中,我将调用 RoutePredicateHandlerMapping,然后将交换变量传递给 WebFilterChainProxy 以在将其传递给 org.springframework.cloud.gateway.handler.FilteringHandler 之前进行身份验证,这非常有效! 我仍然认为我对它进行了过度设计,我希望有一种方法可以使用注释和配置 bean 而不是所有这些自定义的 类(WebHttpHandlerBuilder 和 DispatcherHandler)。

我遇到了类似的问题。公认的解决方案虽然很有趣,但对我来说有点过激。我能够通过在安全配置中的 SecurityWebFiltersOrder.AUTHENTICATION 之前添加我的自定义过滤器来使其工作。这类似于我在常规 Spring mvc 应用程序中成功完成的操作。

下面是一个使用 oauth 身份验证的示例。 tokenIntrospector 是我的自定义内省器,requestInitializationFilter 是获取租户 ID 并将其存储在上下文中的过滤器。

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