前景提示:
①我需要在filter中获取到json数据->对key名首字母进行排序,然后拼接,进行验签
②所以就需要在filer获取到json的数据,因为请求数据是一次性读取的流。如果过滤器中调用了request.json或request.get_json(),控制器将无法再次读取。
③我这种是spring-boot-starter-webflux,它基于 Reactor 库的响应式流模型工作。与传统的 Servlet 过滤器不同,WebFlux 过滤器处理的是异步、非阻塞的请求和响应流。
-
maven
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> -
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;
}
}
- 工具类
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);
}
}
}