注解本身仅作标记,真正实现接口参数解密与验签依赖Spring AOP+自定义注解+反射解析;通过@VerifySign和@DecryptParam声明需求,AOP切面在请求进入Controller前统一读取、校验、解密并重写请求体,确保@RequestBody绑定解密后数据。

Java 中注解本身不能直接实现接口参数的解密与验签,它只是元数据标记;真正起作用的是配合 Spring AOP(或拦截器)+ 自定义注解 + 反射解析,在请求进入 Controller 方法前统一处理加解密和签名验证逻辑。
自定义注解声明解密与验签需求
先定义两个注解,用于标记哪些接口需要自动解密、哪些需要验签:
- @DecryptParam:标注在方法参数上,表示该参数需先解密再绑定
- @VerifySign:标注在方法或类上,表示整个请求体需校验签名
示例:
@PostMapping("/order")
@VerifySign
public Result<Order> createOrder(@DecryptParam @RequestBody OrderRequest req) {
return service.create(req);
}
用 AOP 拦截请求并执行解密与验签
编写一个 @Aspect 切面,匹配带 @VerifySign 或参数含 @DecryptParam 的 Controller 方法:
立即学习“Java免费学习笔记(深入)”;
- 从 HttpServletRequest 中读取原始请求体(注意只能读一次,需用 ContentCachingRequestWrapper 包装)
- 若存在 @VerifySign,提取 header 中的签名(如 X-Signature)、时间戳、随机数,结合约定密钥和请求体原文做 HMAC-SHA256 校验
- 若参数有 @DecryptParam,对对应字段(如 JSON 字符串中的某个字段,或整个 body)使用 AES/RSA 解密
- 将解密后的内容重新构造为 JSON 字符串,替换原 request body,并设置到 ContentCachingRequestWrapper 中供后续 @RequestBody 绑定使用
关键细节:如何让 @RequestBody 绑定解密后的值?
Spring 默认从 request input stream 读取 body,但流只能读一次。解决方式有两种:
- 在切面中用 ContentCachingRequestWrapper 包装 request,把解密后的内容写入缓存 buffer,并重写 getInputStream() 和 getReader() 方法返回新内容
- 不替换 body,而是用 @ModelAttribute + 自定义 WebDataBinder,在参数绑定阶段对特定类型/注解的参数手动解密(更灵活,但需配合 DTO 封装)
推荐第一种,侵入小、复用性高;注意切点表达式要排除静态资源、健康检查等非业务接口。
安全与扩展建议
- 验签时强制校验时间戳(如 5 分钟内有效),防止重放攻击
- 解密密钥不要硬编码,从配置中心或环境变量加载,支持按 AppID 动态获取密钥
- 验签失败或解密异常时,统一返回 401 Unauthorized 或自定义错误码,避免泄露细节
- 可增加注解属性,例如 @VerifySign(algorithm = "HMAC-SHA256", required = true),提升灵活性
不复杂但容易忽略。核心是“注解声明意图 + AOP 执行动作 + 请求体可重放”,三者缺一不可。


















