在过滤器中获取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);
    }
  }
}
相关推荐
大G哥11 分钟前
Rust 之 trait 与泛型的奥秘
java·开发语言·jvm·数据结构·rust
isyangli_blog22 分钟前
(1-1)Java的JDK、JRE、JVM三者间的关系
java·开发语言·jvm
zhuiQiuMX37 分钟前
笔试阶段性心得总结
java·python
星沁城2 小时前
236. 二叉树的最近公共祖先
java·数据结构·leetcode·二叉树
oliveira-time3 小时前
Java 1.8(也称为Java 8)
java·开发语言
极小狐5 小时前
如何使用极狐GitLab 软件包仓库功能托管 maven?
java·运维·数据库·安全·c#·gitlab·maven
.生产的驴5 小时前
SpringBoot 集成滑块验证码AJ-Captcha行为验证码 Redis分布式 接口限流 防爬虫
java·spring boot·redis·分布式·后端·爬虫·tomcat
野犬寒鸦6 小时前
MySQL索引使用规则详解:从设计到优化的完整指南
java·数据库·后端·sql·mysql
思考的橙子6 小时前
Springboot之会话技术
java·spring boot·后端
钰爱&7 小时前
【Linux】POSIX 线程信号量与互斥锁▲
java·开发语言·jvm