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

Spring Reactive WebFlux Filter请求体验证失败无法拦截请求问题求解

问题根因

你当前的写法有三个核心错误,导致校验逻辑无法阻断主请求流程:

  1. 校验逻辑独立于主响应流:你在doOnNext中主动调用clientMono.subscribe(),相当于单独开启了一条异步子流运行校验逻辑,主请求流程不会等待该子流的执行结果,直接就将请求转发给了Controller,子流中抛出的异常完全不会影响主流程。
  2. 请求体读取后未缓存重写:请求体的DataBuffer是一次性可读的,你在Filter中读取后如果不将缓存的请求体重写回请求对象,后续Controller读取请求体时会直接拿到空数据。
  3. 错误信号未传入主流程: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.25 09:06:04