You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Cloud Gateway过滤器中WebClient成功后转发请求问题

问题描述

我的操作

正在用Spring Cloud构建微服务,已搭建Spring Cloud Gateway作为统一入口,在网关里实现了一个过滤器,通过WebClient把所有请求转发到AUTH-SERVICE做认证授权,逻辑如下:

  • 收到200状态码:认证通过,将请求转发到USER-MICROSERVICES等业务服务
  • 收到其他状态码:直接返回该响应给客户端

遇到的问题

因为对响应式编程和WebClient不熟悉,现在碰到两个问题:

  1. 从AUTH-SERVICE收到成功响应时,没法正确执行return chain.filter(exchange),请求没转发到USER-SERVICE,而是直接返回200成功响应
  2. 认证失败时,调试能看到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);
    }
}

额外优化点

  1. WebClient路径修正:原代码中uri(request.getURI().getPath())会使用网关的请求路径,需拼接AUTH-SERVICE的基础地址确保转发正确。
  2. GatewayConfig调整:AUTH-SERVICE的路由无需添加JwtFilter(/authenticate已被标记为开放端点),可去掉对应过滤器配置:
.route("AUTH-SERVICE", r -> r.path("/authenticate/**")
        .uri("http://localhost:9094/"))

内容的提问来源于stack exchange,提问作者Mayur Mayur

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 17:55:15