如何让Spring Cloud Gateway全局过滤器作用于RouterFunction中的WebClient?
针对你在Spring Cloud Gateway(Finchley.M5)里遇到的问题——RouterFunction中的WebClient无法自动应用Gateway的GlobalFilter认证逻辑,这里有两个实用的解决方案,从简单到通用逐步说明:
方案一:直接传递当前请求的X-AUTH-TOKEN头(最快实现)
你的核心需求其实是把当前请求的认证令牌传递给下游服务,这也是最直接的解决思路。WebFlux里没有传统MVC的RequestContextHolder(因为反应式编程是非阻塞无状态的,ThreadLocal不再适用),但ServerRequest本身就包含了当前请求的所有上下文信息,包括请求头,你可以直接从中提取:
修改你的RouterFunction代码,在构建WebClient请求时添加认证头:
@Bean RouterFunction<ServerResponse> routes(WebClient webClient) { log.debug("authServiceUrl:{}", this.authServiceUrl); log.debug("postServiceUrl:{}", this.postServiceUrl); log.debug("favoriteServiceUrl:{}", this.favoriteServiceUrl); return route( GET("/posts/{slug}/favorited"), (req) -> { // 从当前请求中提取X-AUTH-TOKEN String authToken = req.headers().firstHeader("X-AUTH-TOKEN"); Flux<Map> favorites = webClient .get() .uri(favoriteServiceUrl + "/posts/{slug}/favorited", req.pathVariable("slug")) // 将令牌添加到下游请求头中 .header("X-AUTH-TOKEN", authToken) .retrieve() .bodyToFlux(Map.class); Publisher<Map> cb = from(favorites) .commandName("posts-favorites") .fallback(Flux.just(Collections.singletonMap("favorited", false))) .eager() .build(); return ok().body(favorites, Map.class); } ) // 其他路由配置... }
如果需要在多个RouterFunction中复用这个逻辑,可以把提取并添加头的逻辑封装成一个工具方法,或者自定义WebClient的过滤器:
// 封装成可复用的WebClient过滤器 private ExchangeFilterFunction attachAuthToken(ServerRequest req) { return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> { String authToken = req.headers().firstHeader("X-AUTH-TOKEN"); if (authToken != null) { // 修改请求,添加认证头 ClientRequest modifiedRequest = ClientRequest.from(clientRequest) .header("X-AUTH-TOKEN", authToken) .build(); return Mono.just(modifiedRequest); } return Mono.just(clientRequest); }); }
使用时只需要给WebClient添加这个过滤器:
Flux<Map> favorites = webClient .mutate() .filter(attachAuthToken(req)) .build() .get() .uri(favoriteServiceUrl + "/posts/{slug}/favorited", req.pathVariable("slug")) .retrieve() .bodyToFlux(Map.class);
方案二:复用Gateway的GlobalFilter逻辑(更通用)
如果你的Gateway除了认证还有其他GlobalFilter逻辑,想要让WebClient也自动应用这些逻辑,可以把GlobalFilter的核心逻辑抽离出来,适配成WebClient的ExchangeFilterFunction。
举个例子,假设你的认证GlobalFilter是这样的:
@Component public class AuthGlobalFilter implements GlobalFilter, Ordered { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { String token = exchange.getRequest().getHeaders().getFirst("X-AUTH-TOKEN"); // 这里是你的令牌验证逻辑,比如调用认证服务校验 if (token == null || !tokenIsValid(token)) { exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete(); } return chain.filter(exchange); } @Override public int getOrder() { return -100; // 设置优先级,确保认证逻辑先执行 } }
我们可以把验证令牌的核心逻辑抽成独立方法,然后分别在GlobalFilter和WebClient过滤器中调用:
// 抽离通用的认证工具类 public class AuthValidator { // 注入你的认证服务等依赖 @Autowired private AuthService authService; public Mono<Void> validateToken(String token) { if (token == null) { return Mono.error(new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Missing auth token")); } return authService.validateToken(token) .onErrorResume(e -> Mono.error(new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Invalid token"))); } } // 修改GlobalFilter,复用通用逻辑 @Component public class AuthGlobalFilter implements GlobalFilter, Ordered { @Autowired private AuthValidator authValidator; @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { String token = exchange.getRequest().getHeaders().getFirst("X-AUTH-TOKEN"); return authValidator.validateToken(token) .then(chain.filter(exchange)); } @Override public int getOrder() { return -100; } } // 适配成WebClient的过滤器 @Component public class AuthWebClientFilter { @Autowired private AuthValidator authValidator; public ExchangeFilterFunction getAuthFilter() { return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> { String token = clientRequest.headers().getFirst("X-AUTH-TOKEN"); return authValidator.validateToken(token) .then(Mono.just(clientRequest)); }); } }
然后在你的RouterFunction中,构建带有这个过滤器的WebClient:
@Bean RouterFunction<ServerResponse> routes(WebClient.Builder webClientBuilder, AuthWebClientFilter authFilter) { // 构建默认带有认证过滤器的WebClient WebClient webClient = webClientBuilder .filter(authFilter.getAuthFilter()) .build(); return route( GET("/posts/{slug}/favorited"), (req) -> { String authToken = req.headers().firstHeader("X-AUTH-TOKEN"); Flux<Map> favorites = webClient .get() .uri(favoriteServiceUrl + "/posts/{slug}/favorited", req.pathVariable("slug")) .header("X-AUTH-TOKEN", authToken) .retrieve() .bodyToFlux(Map.class); // ... 其他逻辑 return ok().body(favorites, Map.class); } ); }
总结
- 如果你只需要传递认证令牌,方案一是最快速的实现方式,直接从
ServerRequest提取头并添加到WebClient请求即可。 - 如果你需要复用Gateway的多个过滤器逻辑,方案二更合适,通过抽离通用逻辑来实现代码复用,保持Gateway和RouterFunction的逻辑一致性。
另外,你使用的Finchley.M5版本中,WebClient的API是稳定的,上述代码可以正常运行。
内容的提问来源于stack exchange,提问作者Hantsy

