Spring Cloud Gateway过滤器中WebClient成功后转发请求问题
我的操作
正在用Spring Cloud构建微服务,已搭建Spring Cloud Gateway作为统一入口,在网关里实现了一个过滤器,通过WebClient把所有请求转发到AUTH-SERVICE做认证授权,逻辑如下:
- 收到200状态码:认证通过,将请求转发到USER-MICROSERVICES等业务服务
- 收到其他状态码:直接返回该响应给客户端
遇到的问题
因为对响应式编程和WebClient不熟悉,现在碰到两个问题:
- 从AUTH-SERVICE收到成功响应时,没法正确执行
return chain.filter(exchange),请求没转发到USER-SERVICE,而是直接返回200成功响应 - 认证失败时,调试能看到WebClient收到403状态码,但返回给客户端时变成了500状态码
相关代码
JwtFilter.java
@Component public class JwtFilter implements GatewayFilter { @Autowired private JwtUtil jwtUtil; @Autowired private RouterValidator routerValidator; @Resource private WebClient webClient; @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); if (routerValidator.isSecured.test(request)) { if (this.isAuthMissing(request)) return this.onError(exchange, "Authorization header is missing in request", HttpStatus.UNAUTHORIZED); final String token = this.getAuthHeader(request); return webClient.post() .uri(request.getURI().getPath()) .headers(httpHeaders -> { httpHeaders.add("Authorization", token); httpHeaders.add("Content-Type", "application/json"); }) .retrieve() .onStatus(httpStatus -> httpStatus.is4xxClientError(), response -> Mono.error(new HttpClientErrorException(response.statusCode().toString()))) .onStatus(httpStatus -> httpStatus.is5xxServerError(), response -> Mono.error(new HttpClientErrorException(response.statusCode().toString()))) .bodyToMono(Void.class) .doOnSuccess(res -> chain.filter(exchange)); } else return chain.filter(exchange); } private boolean isAuthMissing(ServerHttpRequest request) { return !request.getHeaders().containsKey("Authorization"); } private Mono<Void> onError(ServerWebExchange exchange, String err, HttpStatus httpStatus) { ServerHttpResponse response = exchange.getResponse(); response.setStatusCode(httpStatus); return response.setComplete(); } private String getAuthHeader(ServerHttpRequest request) { return request.getHeaders().getOrEmpty("Authorization").get(0); } }
RouterValidator.java
@Component public class RouterValidator { static String[] arr= {"/authenticate"}; public static final List<String> openApiEndpoints = Arrays.asList(arr); public Predicate<ServerHttpRequest> isSecured = request -> openApiEndpoints .stream() .noneMatch(uri -> request.getURI().getPath().contains(uri)); }
GatewayConfig.java
@Configuration public class GatewayConfig { @Autowired JwtFilter filter; @Bean public RouteLocator routes(RouteLocatorBuilder builder) { return builder.routes() .route("USER-MANAGEMENT", r -> r.path("/createUser/**") .filters(f -> f.filter(filter)) .uri("http://localhost:9092/")) .route("AUTH-SERVICE", r -> r.path("/authenticate/**") .filters(f -> f.filter(filter)) .uri("http://localhost:9094/")) .build(); } }
已尝试方案
Spring Cloud Gateway基于Webflux,不能用RestTemplate;用.block()会抛出java.lang.IllegalStateException: block()/blockFirst()/blockLast() are blocking, which is not supported in thread异常,这个方案行不通。
期望结果
- WebClient从AUTH-SERVICE收到200状态码时,将请求转发到USER-SERVICE
- WebClient收到其他状态码时,直接把该状态码及对应内容返回给客户端
问题1:认证成功后未转发请求的修复
代码中doOnSuccess(res -> chain.filter(exchange))是副作用操作,不会改变响应式流的执行逻辑,它仅在成功时触发一段代码,但不会将chain.filter(exchange)的结果纳入返回流。正确做法是用flatMap串联响应式操作:
将原代码片段:
.bodyToMono(Void.class) .doOnSuccess(res -> chain.filter(exchange));
替换为:
.bodyToMono(Void.class) .flatMap(res -> chain.filter(exchange));
flatMap会等待WebClient的认证请求完成,再执行chain.filter(exchange)并将其结果作为过滤器的最终返回值,确保请求能继续转发到下游业务服务。
问题2:认证失败返回500的修复
原代码在onStatus中抛出HttpClientErrorException,但网关未正确处理该异常,导致返回500内部错误。应该直接将AUTH-SERVICE的响应原样返回给客户端:
修改WebClient的retrieve()部分:
.retrieve() .onStatus(httpStatus -> !httpStatus.is2xxSuccessful(), response -> { return response.bodyToMono(String.class) .flatMap(body -> { ServerHttpResponse gatewayResponse = exchange.getResponse(); gatewayResponse.setStatusCode(response.statusCode()); // 同步AUTH-SERVICE的响应头 response.headers().asHttpHeaders().forEach(gatewayResponse.getHeaders()::add); // 写入AUTH-SERVICE返回的响应内容 DataBuffer buffer = gatewayResponse.bufferFactory().wrap(body.getBytes(StandardCharsets.UTF_8)); return gatewayResponse.writeWith(Mono.just(buffer)); }); })
这样当AUTH-SERVICE返回非2xx状态码时,网关会直接转发对应的状态码、响应头和内容,不会抛出异常导致500错误。
完整修复后的JwtFilter.java
@Component public class JwtFilter implements GatewayFilter { @Autowired private RouterValidator routerValidator; @Resource private WebClient webClient; @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); if (routerValidator.isSecured.test(request)) { if (this.isAuthMissing(request)) return this.onError(exchange, "Authorization header is missing in request", HttpStatus.UNAUTHORIZED); final String token = this.getAuthHeader(request); return webClient.post() // 指定AUTH-SERVICE的完整地址,避免路径错误 .uri("http://localhost:9094" + request.getURI().getPath()) .headers(httpHeaders -> { httpHeaders.add("Authorization", token); httpHeaders.add("Content-Type", "application/json"); }) .retrieve() .onStatus(httpStatus -> !httpStatus.is2xxSuccessful(), response -> { return response.bodyToMono(String.class) .flatMap(body -> { ServerHttpResponse gatewayResponse = exchange.getResponse(); gatewayResponse.setStatusCode(response.statusCode()); response.headers().asHttpHeaders().forEach(gatewayResponse.getHeaders()::add); DataBuffer buffer = gatewayResponse.bufferFactory().wrap(body.getBytes(StandardCharsets.UTF_8)); return gatewayResponse.writeWith(Mono.just(buffer)); }); }) .bodyToMono(Void.class) .flatMap(res -> chain.filter(exchange)); } else { return chain.filter(exchange); } } private boolean isAuthMissing(ServerHttpRequest request) { return !request.getHeaders().containsKey("Authorization"); } private Mono<Void> onError(ServerWebExchange exchange, String err, HttpStatus httpStatus) { ServerHttpResponse response = exchange.getResponse(); response.setStatusCode(httpStatus); DataBuffer buffer = response.bufferFactory().wrap(err.getBytes(StandardCharsets.UTF_8)); return response.writeWith(Mono.just(buffer)); } private String getAuthHeader(ServerHttpRequest request) { return request.getHeaders().getOrEmpty("Authorization").get(0); } }
额外优化点
- WebClient路径修正:原代码中
uri(request.getURI().getPath())会使用网关的请求路径,需拼接AUTH-SERVICE的基础地址确保转发正确。 - GatewayConfig调整:AUTH-SERVICE的路由无需添加JwtFilter(
/authenticate已被标记为开放端点),可去掉对应过滤器配置:
.route("AUTH-SERVICE", r -> r.path("/authenticate/**") .uri("http://localhost:9094/"))
内容的提问来源于stack exchange,提问作者Mayur Mayur

