Spring Reactive WebFlux Filter请求体验证失败无法拦截请求问题求解
问题根因
你当前的写法有三个核心错误,导致校验逻辑无法阻断主请求流程:
- 校验逻辑独立于主响应流:你在
doOnNext中主动调用clientMono.subscribe(),相当于单独开启了一条异步子流运行校验逻辑,主请求流程不会等待该子流的执行结果,直接就将请求转发给了Controller,子流中抛出的异常完全不会影响主流程。 - 请求体读取后未缓存重写:请求体的
DataBuffer是一次性可读的,你在Filter中读取后如果不将缓存的请求体重写回请求对象,后续Controller读取请求体时会直接拿到空数据。 - 错误信号未传入主流程:WebFlux中只有将错误信号传入主响应流,才能触发整条链路的终止逻辑,在独立子流中抛异常只会触发子流的异常处理,无法终止主请求。
解决方案
将HMAC校验逻辑完全串联到主响应流中,先完整读取缓存请求体,校验通过后再将缓存的请求体重写回请求对象转发给后续链路,校验失败直接返回错误信号终止流程。
以下是修正后的Filter代码:
@Slf4j @Component public class HmacFilter implements WebFilter { @Override public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) { String clientName = getClientName(exchange.getRequest()); String requestHmac = getHmac(exchange.getRequest()); // 读取完整请求体并缓存 return DataBufferUtils.join(exchange.getRequest().getBody()) .flatMap(dataBuffer -> { // 读取请求体内容 byte[] bodyBytes = new byte[dataBuffer.readableByteCount()]; dataBuffer.read(bodyBytes); // 手动释放DataBuffer避免内存泄漏 DataBufferUtils.release(dataBuffer); String requestBody = new String(bodyBytes, StandardCharsets.UTF_8); // 校验逻辑直接串联到主流程,无需单独subscribe return clientService.getClient(clientName) .flatMap(client -> { String calculatedHmac = HmacCalculator.calculateHmac(requestBody, client.getSecretKey()); log.info("client : {}, hmac received: {}, hmac calculated: {}, request {}", client.getName(), requestHmac, calculatedHmac, requestBody); if (StringUtils.isBlank(calculatedHmac) || !calculatedHmac.equals(requestHmac)) { log.error("hmac doesn't match for client : {} the request {}, hmac received: {}, hmac calculated: {}", client.getName(), requestHmac, requestBody, calculatedHmac); // 返回错误信号终止主流程 return Mono.error(new OrderServiceException(OrderServiceExceptionCode.INVALID_HMAC)); } log.info("hmac matched successfully client {}", client.getName()); // 校验通过,将缓存的请求体重写回请求对象 ServerHttpRequest newRequest = exchange.getRequest().mutate() .bodyValue(requestBody) .build(); // 转发到后续链路 return chain.filter(exchange.mutate().request(newRequest).build()); }); }); } // 保留你原有获取clientName、getHmac的方法实现 private String getClientName(ServerHttpRequest request) { // 原有逻辑 } private String getHmac(ServerHttpRequest request) { // 原有逻辑 } }
补充注意事项
如果你项目中没有配置全局异常处理器捕获OrderServiceException返回规范错误响应,可以在校验失败时直接构造响应返回,无需抛异常:
if (校验失败) { exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); byte[] errBytes = "{\"code\":\"401\",\"message\":\"HMAC校验失败\"}".getBytes(StandardCharsets.UTF_8); DataBuffer errBuffer = exchange.getResponse().bufferFactory().wrap(errBytes); exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON); return exchange.getResponse().writeWith(Mono.just(errBuffer)); }
内容的提问来源于stack exchange,提问作者vaspaean
相关产品推荐
相关产品推荐

