Spring Gateway预过滤器如何正确返回417错误响应?
故障现象
基于Spring Gateway + Spring WebFlux开发的网关应用,业务要求所有下游服务调用前完成规则校验,校验不通过时直接返回HTTP 417状态码。但现有实现运行后,请求无任何响应输出、始终处于挂起状态,既不返回预期错误,也不转发至下游服务。
现有核心实现代码
@Component public class GlobalGatewayPreFilter extends AbstractGatewayFilterFactory<GlobalGatewayPreFilter.Config> { public GlobalGatewayPreFilter() { super(Config.class); } @Override public GatewayFilter apply(Config config) { return (exchange, chain) -> exchange .getPrincipal() .filter(principal -> principal instanceof UserAuthenticationToken) .cast(UserAuthenticationToken.class) .filter(token -> (token.getUser().isVerified())) .defaultIfEmpty(setErrorResponse(exchange)).flatMap(chain::filter); } private ServerWebExchange setErrorResponse(ServerWebExchange exchange) { exchange.getResponse().setStatusCode(HttpStatus.EXPECTATION_FAILED); exchange.getResponse().setComplete(); return exchange; } public static class Config { private String name; public String getName() { return this.name; } public void setName(String name) { this.name = name; } } }
故障根因
- Reactor异步流时序错误:
defaultIfEmpty是同步值替换操作符,内部调用的setErrorResponse方法中,exchange.getResponse().setComplete()本身是返回Mono<Void>的异步方法。原代码仅做同步方法调用、没有订阅这个Mono的返回值,响应完成信号根本不会被触发。 - 链路执行逻辑错误:校验不通过的分支,原代码将未完成响应写入的exchange透传到了后续的
chain.filter(),导致网关既尝试转发请求到下游,又尝试写入错误响应,两个流程互相竞争、等待信号,最终造成请求永久挂起。 - 流分支语义不匹配:当
getPrincipal()获取不到匹配的认证信息、或者校验不通过时,流进入空分支后,没有正确终止整条过滤器链,反而把链路的控制权又交回给了网关的转发逻辑。
修复方案
调整核心流逻辑为:校验通过时才将请求交回给网关过滤器链,转发到下游服务;校验不通过时直接异步写入417状态响应、返回完成信号终止链路,不再进入下游转发流程。
修复后代码
@Component public class GlobalGatewayPreFilter extends AbstractGatewayFilterFactory<GlobalGatewayPreFilter.Config> { public GlobalGatewayPreFilter() { super(Config.class); } @Override public GatewayFilter apply(Config config) { // 核心校验逻辑 GatewayFilter delegate = (exchange, chain) -> exchange .getPrincipal() // 匹配认证对象类型 .filter(principal -> principal instanceof UserAuthenticationToken) .cast(UserAuthenticationToken.class) // 校验用户是否完成实名认证 .filter(token -> token.getUser().isVerified()) // 校验通过,继续走网关后续过滤器链,转发到下游 .flatMap(token -> chain.filter(exchange)) // 校验不通过,直接返回417错误响应,终止链路 .switchIfEmpty(buildExpectationFailedResponse(exchange)); // 设置最高执行优先级,保证校验逻辑在路由转发前执行 return new OrderedGatewayFilter(delegate, Ordered.HIGHEST_PRECEDENCE); } private Mono<Void> buildExpectationFailedResponse(ServerWebExchange exchange) { ServerHttpResponse response = exchange.getResponse(); // 设置417状态码 response.setStatusCode(HttpStatus.EXPECTATION_FAILED); // 异步返回响应完成信号,作为流的终止节点 return response.setComplete(); } public static class Config { private String name; public String getName() { return this.name; } public void setName(String name) { this.name = name; } } }
关键注意事项
ServerHttpResponse.setComplete()必须作为流的终止节点返回,不能在同步逻辑中无订阅调用,否则响应完成信号永远不会触发。- 错误分支处理完成后,绝对不要将exchange传入
chain.filter(),否则会出现响应写入和请求转发的逻辑竞争,大概率引发请求挂起。 - 全局前置校验的过滤器需要设置足够高的执行优先级,确保校验逻辑在路由匹配、下游转发动作之前执行。
内容的提问来源于stack exchange,提问作者nicholasnet
相关产品推荐
相关产品推荐

