更改 Spring Security WebFilter 的顺序
Posted
技术标签:
【中文标题】更改 Spring Security WebFilter 的顺序【英文标题】:Changing the Order of the Spring Security WebFilter 【发布时间】:2019-03-09 07:01:38 【问题描述】:我有一个使用 Spring Cloud Gateway 实现的 API Gateway,它使用 Spring Security。 WebFlux 的 Spring Security 在过滤器链的开头作为 WebFilter 实现。所以在认证成功后,请求会被转发到 Spring Cloud Gateway 的 RoutePredicateHandlerMapping,它会尝试根据 URL 模式推断目的地,然后它会去 FilteringWebHandler 执行 Spring Cloud Gateway 的其他过滤器。
我的问题如下:我已经实现了一个自定义的身份验证算法,它使用查询字符串和标头变量作为根据项目要求进行身份验证的凭据,这是没有任何问题的。当我们需要为独立于路径的身份验证算法添加一个小的自定义项时,就会出现问题。当请求到达 Spring Security 的 WebFilter 时,模式匹配还没有完成所以我不知道它指向哪个应用程序,例如:
app1:
-路径:/app1/**
应用程序2:
-路径:/app2/**
这意味着我应该进行路由映射-> 身份验证-> 过滤Web 处理程序,而不是进行身份验证-> 路由映射-> 过滤Web 处理程序。并不是说这三个组件不相似,其中一个是过滤器,另一个是映射器,最后一个是 Web 处理程序。现在我知道如何自定义它们,但问题是我不知道如何拦截 Netty 服务器构建过程以更改这些操作的顺序。我需要等待构建过程结束并在服务器启动之前更改服务器的内容。我该怎么做?
【问题讨论】:
【参考方案1】:我遇到了类似的问题。接受的解决方案虽然很有趣,但对我来说有点激烈。我可以通过在安全配置中的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
【讨论】:
【参考方案2】:编辑:这是最终解决方案: 所以我是这样做的:
目标:从默认的HttpHandler中去掉Spring Security的WebFilter,插入到RoutePredicateRouteMapping和Spring Cloud Gateway的FilteringWebHandler之间
为什么:因为我在进行自定义身份验证过程时需要知道应用程序 ID。 RoutePredicateRouteMapping 通过将请求的 URL 与预定义列表匹配,将此应用程序 ID 附加到请求。
我是怎么做到的: 1- 移除 Spring Security 的 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 Web 的 FilteringWebHandler 和添加的 WebFilter 包装 Spring Cloud Gateway 的 FilteringWebHandler 我创建了自己的 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 管道获得更好的性能,我认为这是面向未来的
结束编辑
WebFlux 的 Spring Security 实现为 WebFilter,几乎在收到请求后立即执行。我已经实现了自定义身份验证转换器和身份验证管理器,它们将从标头和 URL 中提取一些变量并将它们用于身份验证。这工作没有任何问题。
现在我需要在身份验证完成之前添加另一个取自 RoutePredicateRouteMapping 的变量。我真正想要的是从当前位置删除 WebFilter(称为 WebFilterChainProxy)并将其放在 RoutePredicateRouteMapping 和 FilteringWeHandler 之间。
默认流程如下:
ChannelOperations 调用 ReactorHttpHandlerAdapter,后者调用 HttpWebHandlerAdapter、ExceptionHandlingWebHandler,然后是 org.springframework.web.server.handler.FilterWebHandler。
此 WebHandler 将调用其过滤器,然后调用 DispatchHandler。其中一个过滤器是为 Spring Security 进行身份验证的 WebFilterChainProxy。所以第一步是从这里移除过滤器。
现在在过滤器之后调用的 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)。
【讨论】:
【参考方案3】:您可能应该将该安全过滤器实现为适当的GatewayFilter
,因为只有那些知道其他GatewayFilter
实例并且可以相应地排序。在您的情况下,您可能希望在路由之后订购它。
另外,please don't cross-post,Spring 团队正在积极监控 ***。
【讨论】:
安全过滤器不是我的,是Spring Security自带的web过滤器。您是否认为 permitAll() 请求然后创建一个具有最高顺序的 GlobalFilter (在所有其他过滤器之前执行),然后手动调用 Spring Security 的过滤器以进行身份验证和(大多数重要的是)填写身份验证详细信息?或者这会给未来的 Spring Security 实现带来问题? 我彻底考虑过将其实现为 GlobalFilter 但这不会削减它,原因有两个:1-我需要一种方法从其位置删除默认过滤代理 2-调用身份验证代理GlobalFilter 需要我在 GlobalFilter 中不可用的处理程序请参阅我上面的答案。我认为该解决方案将在未来得到完善,但基本上它的工作方式与我描述的一样。以上是关于更改 Spring Security WebFilter 的顺序的主要内容,如果未能解决你的问题,请参考以下文章
更改 Spring Security WebFilter 的顺序
Spring Security:添加“伪登录”以更改用户信息