Spring Boot 一个注解搞定「加密 + 解密 + 签名 + 验签」
本文基于 Spring Boot 3.x,通过一个自定义注解 + AOP,一行注解 即可给任何 Controller 方法加上
请求解密 → 验签 → 响应加密 → 加签 的完整链路,并可直接拷贝到生产环境使用。
一、最终效果
java
@PostMapping("/order")
@ApiSecurity(decryptRequest = true, encryptResponse = true) // ← 就这么一行
public OrderResp createOrder(@RequestBody OrderReq req) {
return service.create(req);
}
- 请求体:RSA 加密后的 AES 密钥 + AES 加密后的业务 JSON + 签名
- 框架自动完成 解密 → 验签 → 业务处理 → 响应加密 → 加签
- 零侵入,老接口想加安全,贴一个注解即可。
二、传输对象
java
@Data
public class ApiSecurityParam {
private String appId; // 应用标识
private String key; // RSA 加密后的 AES 密钥(Base64)
private String data; // AES 加密的业务 JSON(Base64)
private String sign; // 签名
private String timestamp; // 防重放
private String nonce; // 防重放
}
三、核心注解
java
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiSecurity {
boolean decryptRequest() default false; // 请求体是否解密
boolean encryptResponse() default false; // 响应体是否加密
boolean sign() default true; // 是否验签/加签
}
四、AOP 切面(RequestBodyAdvice + ResponseBodyAdvice)
同时解决 InputStream 只能读一次 的问题。
4.1 解密 & 验签 RequestBodyAdvice
java
@RestControllerAdvice
@Order(Ordered.HIGHEST_PRECEDENCE)
public class DecryptRequestAdvice implements RequestBodyAdvice {
@Override
public boolean supports(MethodParameter methodParameter, Type targetType,
Class<? extends HttpMessageConverter<?>> converterType) {
return methodParameter.hasMethodAnnotation(ApiSecurity.class)
&& methodParameter.getMethodAnnotation(ApiSecurity.class).decryptRequest();
}
@Override
public Object handleEmptyBody(Object body, HttpInputMessage inputMessage,
MethodParameter parameter, Type targetType,
Class<? extends HttpMessageConverter<?>> converterType) {
return body;
}
@SneakyThrows
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage,
MethodParameter parameter, Type targetType,
Class<? extends HttpMessageConverter<?>> converterType) {
String body = StreamUtils.copyToString(inputMessage.getBody(), StandardCharsets.UTF_8);
ApiSecurityParam param = JSON.parseObject(body, ApiSecurityParam.class);
// 1. 防重放校验(timestamp、nonce)
checkReplay(param);
// 2. RSA 私钥解密 AES 密钥
String aesKey = RSAUtil.decryptByPrivateKey(param.getKey(), RsaKeyHolder.PRIVATE_KEY);
// 3. AES 解密业务 JSON
String json = AESUtil.decrypt(param.getData(), aesKey);
// 4. 验签
boolean ok = RSAUtil.verify(json + param.getTimestamp() + param.getNonce(),
RsaKeyHolder.PUBLIC_KEY, param.getSign());
if (!ok) throw new BizException("验签失败");
return new MappingJacksonInputMessage(new ByteArrayInputStream(json.getBytes()),
inputMessage.getHeaders());
}
}
4.2 加密 & 加签 ResponseBodyAdvice
java
@RestControllerAdvice
@Order(Ordered.HIGHEST_PRECEDENCE)
public class EncryptResponseAdvice implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter returnType,
Class<? extends HttpMessageConverter<?>> converterType) {
ApiSecurity anno = returnType.getMethodAnnotation(ApiSecurity.class);
return anno != null && anno.encryptResponse();
}
@SneakyThrows
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType,
MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response) {
String json = JSON.toJSONString(body);
// 1. 随机 AES 密钥
String aesKey = AESUtil.randomKey(128);
// 2. AES 加密响应
String data = AESUtil.encrypt(json, aesKey);
// 3. RSA 公钥加密 AES 密钥
String encKey = RSAUtil.encryptByPublicKey(aesKey, RsaKeyHolder.PUBLIC_KEY);
// 4. 生成签名
String sign = RSAUtil.sign(json, RsaKeyHolder.PRIVATE_KEY);
ApiSecurityParam resp = new ApiSecurityParam();
resp.setKey(encKey);
resp.setData(data);
resp.setSign(sign);
resp.setTimestamp(String.valueOf(System.currentTimeMillis()));
return resp;
}
}
五、工具类速览
- RSAUtil :
encrypt/decrypt
+sign/verify
- AESUtil :
encrypt/decrypt
支持 PKCS5Padding - RsaKeyHolder :从
application.yml
或 KMS 读取公私钥
六、性能 & 安全小贴士
点 | 建议 |
---|---|
对称加密 | AES-128-CBC/PKCS5Padding |
非对称 | RSA-2048 |
防重放 | timestamp ±5 min + nonce 一次性 |
密钥轮换 | 每日定时任务刷新 RSA 密钥对 |
性能 | AES 每次随机 IV,RSA 只加密 128bit 密钥,无压力 |
七、小结
通过以上 一个注解 + 两个 Advice ,在 Spring Boot 中实现 企业级安全传输:
- 0 侵入:老接口贴注解即可
- 高可扩展:支持 GET/POST、Header 传参、自定义算法
- 已落地 :可直接封装为
spring-boot-starter-security-api
,全公司复用。
源码示例已上传 GitHub:
https://github.com/your-org/spring-boot-api-security-starter