Spring Boot 一個(gè)注解搞定加密 + 解密 + 簽名 + 驗(yàn)簽(一文全解)
Spring Boot 一個(gè)注解搞定「加密 + 解密 + 簽名 + 驗(yàn)簽」
本文基于 Spring Boot 3.x,通過一個(gè)自定義注解 + AOP,一行注解即可給任何 Controller 方法加上
請(qǐng)求解密 → 驗(yàn)簽 → 響應(yīng)加密 → 加簽 的完整鏈路,并可直接拷貝到生產(chǎn)環(huán)境使用。
一、最終效果
@PostMapping("/order")
@ApiSecurity(decryptRequest = true, encryptResponse = true) // ← 就這么一行
public OrderResp createOrder(@RequestBody OrderReq req) {
return service.create(req);
}- 請(qǐng)求體:RSA 加密后的 AES 密鑰 + AES 加密后的業(yè)務(wù) JSON + 簽名
- 框架自動(dòng)完成 解密 → 驗(yàn)簽 → 業(yè)務(wù)處理 → 響應(yīng)加密 → 加簽
- 零侵入,老接口想加安全,貼一個(gè)注解即可。
二、傳輸對(duì)象
@Data
public class ApiSecurityParam {
private String appId; // 應(yīng)用標(biāo)識(shí)
private String key; // RSA 加密后的 AES 密鑰(Base64)
private String data; // AES 加密的業(yè)務(wù) 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; // 請(qǐng)求體是否解密
boolean encryptResponse() default false; // 響應(yīng)體是否加密
boolean sign() default true; // 是否驗(yàn)簽/加簽
}
四、AOP 切面(RequestBodyAdvice + ResponseBodyAdvice)
同時(shí)解決 InputStream 只能讀一次 的問題。
4.1 解密 & 驗(yàn)簽 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 HttpMessageConverter<?>> converterType) {
String body = StreamUtils.copyToString(inputMessage.getBody(), StandardCharsets.UTF_8);
ApiSecurityParam param = JSON.parseObject(body, ApiSecurityParam.class);
// 1. 防重放校驗(yàn)(timestamp、nonce)
checkReplay(param);
// 2. RSA 私鑰解密 AES 密鑰
String aesKey = RSAUtil.decryptByPrivateKey(param.getKey(), RsaKeyHolder.PRIVATE_KEY);
// 3. AES 解密業(yè)務(wù) JSON
String json = AESUtil.decrypt(param.getData(), aesKey);
// 4. 驗(yàn)簽
boolean ok = RSAUtil.verify(json + param.getTimestamp() + param.getNonce(),
RsaKeyHolder.PUBLIC_KEY, param.getSign());
if (!ok) throw new BizException("驗(yàn)簽失敗");
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. 隨機(jī) AES 密鑰
String aesKey = AESUtil.randomKey(128);
// 2. AES 加密響應(yīng)
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 讀取公私鑰
六、性能 & 安全小貼士
| 點(diǎn) | 建議 |
|---|---|
| 對(duì)稱加密 | AES-128-CBC/PKCS5Padding |
| 非對(duì)稱 | RSA-2048 |
| 防重放 | timestamp ±5 min + nonce 一次性 |
| 密鑰輪換 | 每日定時(shí)任務(wù)刷新 RSA 密鑰對(duì) |
| 性能 | AES 每次隨機(jī) IV,RSA 只加密 128bit 密鑰,無壓力 |
七、小結(jié)
通過以上 一個(gè)注解 + 兩個(gè) Advice,在 Spring Boot 中實(shí)現(xiàn) 企業(yè)級(jí)安全傳輸:
- 0 侵入:老接口貼注解即可
- 高可擴(kuò)展:支持 GET/POST、Header 傳參、自定義算法
- 已落地:可直接封裝為
spring-boot-starter-security-api,全公司復(fù)用。
源碼示例已上傳 GitHub:https://github.com/your-org/spring-boot-api-security-starter
到此這篇關(guān)于Spring Boot 一個(gè)注解搞定「加密 + 解密 + 簽名 + 驗(yàn)簽」的文章就介紹到這了,更多相關(guān)Spring Boot注解加密 解密 簽名 驗(yàn)簽內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringBoot利用自定義注解實(shí)現(xiàn)隱私數(shù)據(jù)脫敏(加密顯示)的解決方案
- SpringBoot自定義注解API數(shù)據(jù)加密和簽名校驗(yàn)
- SpringBoot中SM2公鑰加密、私鑰解密的實(shí)現(xiàn)示例詳解
- SpringBoot配置文件密碼加密與解密的操作代碼
- SpringBoot進(jìn)行數(shù)據(jù)加密和解密的詳細(xì)指南
- SpringBoot請(qǐng)求參數(shù)加密、響應(yīng)參數(shù)解密的實(shí)現(xiàn)
- Spring Boot 接口參數(shù)加密解密的實(shí)現(xiàn)方法
相關(guān)文章
Java輸出通過InetAddress獲得的IP地址數(shù)組詳細(xì)解析
由于byte被認(rèn)為是unsigned byte,所以最高位的1將會(huì)被解釋為符號(hào)位,另外Java中存儲(chǔ)是按照補(bǔ)碼存儲(chǔ),所以1000 0111會(huì)被認(rèn)為是補(bǔ)碼形式,轉(zhuǎn)換成原碼便是1111 0001,轉(zhuǎn)換成十進(jìn)制數(shù)便是-1212013-09-09
jstl之map,list訪問遍歷以及el表達(dá)式map取值的實(shí)現(xiàn)
下面小編就為大家?guī)硪黄猨stl之map,list訪問遍歷以及el表達(dá)式map取值的實(shí)現(xiàn)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-03-03
Mybatis實(shí)現(xiàn)單個(gè)和批量定義別名typeAliases
這篇文章主要介紹了Mybatis實(shí)現(xiàn)單個(gè)和批量定義別名typeAliases,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09
淺析如何在IDEA中高效使用Test注解進(jìn)行單元測(cè)試
在軟件開發(fā)過程中,單元測(cè)試是保證代碼質(zhì)量的重要手段之一,那么如何在IDEA中高效使用Test注解進(jìn)行單元測(cè)試呢,下面小編就來和大家簡單講講2025-04-04

