在过滤器中获取body中的json数据并且使得后续的controller层也能获取使用

前景提示:

①我需要在filter中获取到json数据->对key名首字母进行排序,然后拼接,进行验签

②所以就需要在filer获取到json的数据,因为请求数据是一次性读取的流。如果过滤器中调用了request.json或request.get_json(),控制器将无法再次读取。

③我这种是spring-boot-starter-webflux,它基于 Reactor 库的响应式流模型工作。与传统的 Servlet 过滤器不同,WebFlux 过滤器处理的是异步、非阻塞的请求和响应流。

  1. maven

    <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency>
  2. filter

java 复制代码
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.Security;
import java.security.Signature;
import java.security.spec.X509EncodedKeySpec;

public class JwtFilter implements WebFilter, Ordered {

  @Override
  public Mono<Void> filter(ServerWebExchange exchange, @NonNull WebFilterChain chain) {
	final ServerHttpRequest serverRequest = exchange.getRequest();
	if (MediaType.APPLICATION_JSON.equals(serverRequest.getHeaders().getContentType())) {
        return DataBufferUtils.join(exchange.getRequest().getBody())
                .flatMap(dataBuffer -> {
                  byte[] bytes = new byte[dataBuffer.readableByteCount()];
                  dataBuffer.read(bytes);
                  DataBufferUtils.release(dataBuffer); // 释放 DataBuffer
                  String body = new String(bytes, StandardCharsets.UTF_8);
                  // 验签逻辑
                  byte[] data = SignUtil.buildPlainTextFromJson(body).getBytes();
                  boolean huaWeiVerifyResult = verify(data, huaWeiProperty.getPublicKey(), headerHuaWeiSignType);
                  if (!huaWeiVerifyResult) {
                    return Mono.error(new RespException(RespStatusEnum.INVALID_TOKEN, "华为验签失败"));
                  }

                  // 缓存后的请求体包装回请求
                  ServerHttpRequest decoratedRequest = new ServerHttpRequestDecorator(exchange.getRequest()) {
                    @Override
                    public Flux<DataBuffer> getBody() {
                      return Flux.just(exchange.getResponse().bufferFactory().wrap(bytes));
                    }
                  };

                  // 替换后的 exchange
                  ServerWebExchange mutatedExchange = exchange.mutate().request(decoratedRequest).build();

                  return chain.filter(mutatedExchange)
                          .contextWrite(ctx -> ctx.put(ContextName.USER, new CustomerUserDetail())
                                  .put(ContextName.IP, ipAddress));
                });
      }
}

  @Override
  public int getOrder() {
    return 0;
  }

  /**
   * 主要是通过此方法来验证签名
   *
   * @param data
   * @param publicKey
   * @param sign
   * @return
   */
  public static boolean verify(byte[] data, String publicKey, String sign) {
    boolean flag = false;
    try {
      // 解密由base64编码的公钥
      byte[] bytes = org.apache.commons.codec.binary.Base64.decodeBase64(publicKey);
      X509EncodedKeySpec keySpec = new X509EncodedKeySpec(bytes);
      KeyFactory factory = KeyFactory.getInstance("RSA");
      PublicKey key = factory.generatePublic(keySpec);
      // 用公钥验证数字签名
      Security.addProvider(new BouncyCastleProvider());
      Signature signature = Signature.getInstance("SHA256withRSA/PSS", "BC");
      signature.initVerify(key);
      signature.update(data);
      byte[] signByte = org.apache.commons.codec.binary.Base64.decodeBase64(sign);
      flag = signature.verify(signByte);
    } catch (Exception e) {
      log.error("verify error:", e);
    }
    return flag;
  }

}
  1. 工具类
java 复制代码
public class SignUtil {

  private static final ObjectMapper objectMapper = new ObjectMapper();

  public static String buildPlainTextFromJson(String jsonBody) {
    try {
      // 将 JSON 转换为 Map
      Map<String, Object> map = objectMapper.readValue(jsonBody, new TypeReference<Map<String, Object>>() {
      });

      // 将 key 排序
      List<String> sortedKeys = new ArrayList<>(map.keySet());
      Collections.sort(sortedKeys);

      // 拼接明文
      StringBuilder sb = new StringBuilder();
      for (String key : sortedKeys) {
        Object value = map.get(key);

        // 如果是数组,格式化为 JSON 数组字符串
        if (value instanceof List || value.getClass().isArray()) {
          sb.append(key).append("=").append(objectMapper.writeValueAsString(value));
        } else {
          sb.append(key).append("=").append(value);
        }
        sb.append("&");
      }

      // 删除最后一个 &
      if (sb.length() > 0) {
        sb.setLength(sb.length() - 1);
      }

      return sb.toString();

    } catch (Exception e) {
      throw new RuntimeException("构建签名明文失败", e);
    }
  }
}
相关推荐
武子康7 分钟前
Java-82 深入浅出 MySQL 内部架构:服务层、存储引擎与文件系统全覆盖
java·开发语言·数据库·学习·mysql·spring·微服务
Rancemy7 分钟前
rabbitmq 03
java·分布式·rabbitmq
Dcs2 小时前
“SQL注入即服务”:一个10年历史系统的奇幻演变
java
秃了也弱了。2 小时前
reflections:Java非常好用的反射工具包
java·开发语言
Amagi.3 小时前
Java设计模式-代理模式
java·代理模式
Joker—H3 小时前
【Java】Reflection反射(代理模式)
java·开发语言·经验分享·代理模式·idea
阿里巴巴淘系技术团队官网博客4 小时前
面向互联网2C业务的分布式类Manus Java框架
java·开发语言·分布式
躲在云朵里`4 小时前
Java面试题(中等)
java
懂得节能嘛.4 小时前
【SpringAI实战】实现仿DeepSeek页面对话机器人(支持多模态上传)
java·spring
张乔244 小时前
mybatisX的自定义模板生成
java·ide·intellij-idea