开发者

Spring Boot 一个注解搞定加密 + 解密 + 签名 + 验签(一文全解)

目录
  • Spring Boot 一个注解搞定「加密 + 解密 + 签名 + 验签」
    • 一、最终效果
    • 二、传输对象
    • 三、核心注解
    • 四、AOP 切面(RequestBodyAdvice + ResponseBodyAdvice)
      • 4.1 解密 & 验签 RequestBodyAdvice
      • 4.2 加密 & 加签 ResponseBodyAdvice
    • 五、工具类速览
      • 六、性能 & 安全小贴士
        • 七、小结

        Spring Boot 一个注解搞定「加密 + 解密 + 签名 + 验签」

        本文基于 Spring Boot 3.x,通过一个自定义注解 + AOP,一行注解即可给任何 Controller 方法加上

        请求解密 → 验签 → 响应加密 → 加签 的完整链路,并可直接拷贝到生产环境使用。

        一、最终效果

        @PostMapping("/order")
        @ApiSecurity(decryptRequest = true, encryptResponse = true)   // ← 就这么一行
        public OrderResp createOrder(@RequestBody OrderReq req) {
            return service.create(req);
        }
        • 请求体:RSA 加密后的 AES 密钥 + AES 加密后的业务 jsON + 签名
        • 框架自动完成 解密 → 验签 → 业务处理 → 响应加密 → 加签
        • 零侵入,老接口想加安全,贴一个注解即可。

        二、传输对象

        @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;      // 防重放
        }
        

        三、核心注解

        @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

        @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 HttpMessageCandroidonverter<?>> converterType) {
                String body = StreamUtils.copyToString(inputMessage.getBody(), StandardCharsets.UTF_8);
                ApiSecurityParam param = JSON.parseobject(body, ApiSecurityParam.class);
                // 1. 防重放校验(timestamp、noGZfCNaOPlCnce)
                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

        @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 www.devze.com= 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;
            }
        }

        五、工具类速览

        • RSAUtilencrypt/decrypt + sign/verify
        • AESUtilencrypt/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-bhttp://www.devze.comoot-api-security-starter

        到此这篇关于Spring Boot 一个注解搞定「加密 + 解密 + 签名 + 验签」的文章就介绍到这了,更多相关Spring Boot注解加密 解密 签名 验签内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!

        0

        上一篇:

        下一篇:

        精彩评论

        暂无评论...
        验证码 换一张
        取 消

        最新开发

        开发排行榜