更改Spring Security WebFilter的顺序

时间:2018-10-04 00:29:34

标签: spring-security netty spring-webflux spring-cloud-gateway

更改Spring Security WebFilter的顺序

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

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

app1:

-路径:/ app1 / **

app2:

-路径:/ app2 / **

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

3 个答案:

答案 0 :(得分:1)

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

目标:从默认的HttpHandler中删除Spring Security的WebFilter,并将其插入到RoutePredicateRouteMapping和Spring Cloud Gateway的FilteringWebHandler之间

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

我是如何做到的: 1-删除Spring Security的WebFilter 我创建了一个HttpHandler bean,它调用默认的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-将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管道获得更好的性能,我认为这种管道可以应对未来的需求

结束编辑

Spring Security for 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,后者将分析路由并为我提供所需的路由ID,然后将其称为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,效果很好! 我仍然认为我已经对它进行了过度设计,希望有一种方法可以使用注释和配置bean来代替所有这些自定义类(WebHttpHandlerBuilder和DispatcherHandler)。

答案 1 :(得分:0)

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

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

答案 2 :(得分:0)

我有类似的问题。可接受的解决方案虽然很有趣,但对我来说却有点过激。通过在安全配置中的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
    }
}