在过滤器中获取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);
    }
  }
}
相关推荐
fanged4 小时前
构建系统maven
java·maven
沙滩小岛小木屋4 小时前
maven编译时跳过test过程
java·maven
江沉晚呤时5 小时前
SQL Server 事务详解:概念、特性、隔离级别与实践
java·数据库·oracle·c#·.netcore
还是鼠鼠5 小时前
单元测试-概述&入门
java·开发语言·后端·单元测试·log4j·maven
MyikJ7 小时前
Java求职面试:从Spring到微服务的技术挑战
java·数据库·spring boot·spring cloud·微服务·orm·面试技巧
MyikJ7 小时前
Java 面试实录:从Spring到微服务的技术探讨
java·spring boot·微服务·kafka·spring security·grafana·prometheus
ShiinaMashirol8 小时前
代码随想录打卡|Day50 图论(拓扑排序精讲 、dijkstra(朴素版)精讲 )
java·图论
cui_hao_nan8 小时前
Nacos实战——动态 IP 黑名单过滤
java
惜.己8 小时前
MySql(十一)
java·javascript·数据库
10000hours9 小时前
【存储基础】NUMA架构
java·开发语言·架构