要在 Spring Boot 中通过一个注解实现加解密与签名功能,我们可以利用AOP(面向切面编程) 来拦截注解标注的方法,在方法执行前后处理加解密和签名逻辑。
一、核心思路
- 自定义注解:定义一个注解(如@CryptoSign),用于标记需要加解密和签名的方法。
- AOP 切面:编写切面类,拦截标注了@CryptoSign的方法,实现:入参解密:对请求参数进行解密处理。出参与密与签名:对响应结果进行加密,并生成签名(如 MD5、SHA256、RSA 等)。签名验证:对请求中的签名进行验证(可选,根据业务需求)。
- 加解密工具类:封装对称加密(如 AES)、非对称加密(如 RSA)和签名算法(如 HmacSHA256、RSA 签名)的工具方法。

二、具体实现
1. 引入依赖
在pom.xml中引入 Spring AOP、加密相关依赖:
<dependencies>
<!-- Spring Boot Starter AOP -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- 加密相关(如BouncyCastle,可选) -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.70</version>
</dependency>
<!-- JSON处理(如FastJSON) -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.32</version>
</dependency>
</dependencies>
2. 自定义注解@CryptoSign
定义注解,用于标记需要处理加解密和签名的方法:
import java.lang.annotation.*;
/**
* 加解密与签名注解
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CryptoSign {
/**
* 加密算法类型(如AES、RSA)
*/
String encryptAlgorithm() default "AES";
/**
* 签名算法类型(如HmacSHA256、RSA)
*/
String signAlgorithm() default "HmacSHA256";
/**
* 是否验证请求签名
*/
boolean verifySign() default true;
}
3. 加解密与签名工具类
封装 AES 加密、HmacSHA256 签名等工具方法(可根据需求扩展 RSA 等算法):
import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.security.MessageDigest;
import java.security.Security;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
/**
* 加解密与签名工具类
*/
public class CryptoUtils {
// 密钥(实际项目中提议从配置文件读取,且定期轮换)
private static final String AES_KEY = "1234567890123456"; // AES-128需16位密钥
private static final String HMAC_KEY = "hmacKey1234567890"; // Hmac密钥
static {
// 注册BouncyCastleProvider(可选,用于扩展算法)
Security.addProvider(new BouncyCastleProvider());
}
/**
* AES加密
*/
public static String aesEncrypt(String content) throws Exception {
Key key = new SecretKeySpec(AES_KEY.getBytes(StandardCharsets.UTF_8), "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] encryptBytes = cipher.doFinal(content.getBytes(StandardCharsets.UTF_8));
return bytesToHex(encryptBytes);
}
/**
* AES解密
*/
public static String aesDecrypt(String encryptContent) throws Exception {
Key key = new SecretKeySpec(AES_KEY.getBytes(StandardCharsets.UTF_8), "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] decryptBytes = cipher.doFinal(hexToBytes(encryptContent));
return new String(decryptBytes, StandardCharsets.UTF_8);
}
/**
* HmacSHA256签名
*/
public static String hmacSHA256Sign(String content) throws Exception {
SecretKeySpec keySpec = new SecretKeySpec(HMAC_KEY.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(keySpec);
byte[] signBytes = mac.doFinal(content.getBytes(StandardCharsets.UTF_8));
return bytesToHex(signBytes);
}
/**
* 验证HmacSHA256签名
*/
public static boolean verifyHmacSHA256Sign(String content, String sign) throws Exception {
String calculateSign = hmacSHA256Sign(content);
return calculateSign.equalsIgnoreCase(sign);
}
/**
* 字节数组转16进制字符串
*/
public static String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
/**
* 16进制字符串转字节数组
*/
public static byte[] hexToBytes(String hex) {
int len = hex.length();
byte[] bytes = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
bytes[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4) + Character.digit(hex.charAt(i + 1), 16));
}
return bytes;
}
/**
* MD5摘要(可选,用于辅助签名)
*/
public static String md5(String content) throws Exception {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] md5Bytes = md.digest(content.getBytes(StandardCharsets.UTF_8));
return bytesToHex(md5Bytes);
}
}
4. AOP 切面实现
编写切面类,拦截@CryptoSign注解的方法,处理入参解密、出参与密和签名验证:
import com.alibaba.fastjson.JSON;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Map;
/**
* 加解密与签名切面
*/
@Aspect
@Component
public class CryptoSignAspect {
/**
* 定义切入点:拦截标注了@CryptoSign的方法
*/
@Pointcut("@annotation(com.example.demo.annotation.CryptoSign)")
public void cryptoSignPointcut() {}
/**
* 环绕通知:处理加解密与签名逻辑
*/
@Around("cryptoSignPointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
// 1. 获取注解信息
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
CryptoSign cryptoSign = method.getAnnotation(CryptoSign.class);
String encryptAlgorithm = cryptoSign.encryptAlgorithm();
String signAlgorithm = cryptoSign.signAlgorithm();
boolean verifySign = cryptoSign.verifySign();
// 2. 处理入参解密与签名验证
Object[] args = joinPoint.getArgs();
if (args != null && args.length > 0) {
for (int i = 0; i < args.length; i++) {
Object arg = args[i];
if (arg instanceof Map) { // 假设入参为Map,包含加密内容和签名(实际可根据业务调整)
Map<String, String> paramMap = (Map<String, String>) arg;
String encryptContent = paramMap.get("content");
String sign = paramMap.get("sign");
// 解密内容
String decryptContent;
if ("AES".equals(encryptAlgorithm)) {
decryptContent = CryptoUtils.aesDecrypt(encryptContent);
} else {
throw new UnsupportedOperationException("不支持的加密算法:" + encryptAlgorithm);
}
// 验证签名
if (verifySign) {
boolean signValid;
if ("HmacSHA256".equals(signAlgorithm)) {
signValid = CryptoUtils.verifyHmacSHA256Sign(decryptContent, sign);
} else {
throw new UnsupportedOperationException("不支持的签名算法:" + signAlgorithm);
}
if (!signValid) {
throw new SecurityException("签名验证失败");
}
}
// 替换入参为解密后的内容(实际可根据业务调整,如转为实体类)
args[i] = decryptContent;
}
}
}
// 3. 执行目标方法
Object result = joinPoint.proceed(args);
// 4. 处理出参与密与签名
if (result != null) {
// 将结果转为JSON字符串(实际可根据业务调整)
String resultStr = JSON.toJSONString(result);
// 加密内容
String encryptResult;
if ("AES".equals(encryptAlgorithm)) {
encryptResult = CryptoUtils.aesEncrypt(resultStr);
} else {
throw new UnsupportedOperationException("不支持的加密算法:" + encryptAlgorithm);
}
// 生成签名
String sign;
if ("HmacSHA256".equals(signAlgorithm)) {
sign = CryptoUtils.hmacSHA256Sign(resultStr);
} else {
throw new UnsupportedOperationException("不支持的签名算法:" + signAlgorithm);
}
// 构造响应结果(包含加密内容和签名)
return Map.of("encryptContent", encryptResult, "sign", sign);
}
return result;
}
}
5. 业务方法使用注解
编写 Controller 或 Service 方法,使用@CryptoSign注解标记:
import com.example.demo.annotation.CryptoSign;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@RestController
public class CryptoController {
/**
* 测试加解密与签名接口
*/
@PostMapping("/testCrypto")
@CryptoSign(encryptAlgorithm = "AES", signAlgorithm = "HmacSHA256", verifySign = true)
public String testCrypto(@RequestBody Map<String, String> param) {
// 入参已被切面解密,此处直接处理业务逻辑
String content = (String) param; // 实际切面中已替换入参为解密后的字符串
return "处理结果:" + content;
}
}
三、关键扩展点
- 算法扩展:可在CryptoUtils中扩展 RSA 非对称加密、RSA 签名、SM4(国密)等算法,只需在注解和切面中增加对应算法的判断逻辑。
- 参数格式优化:实际项目中,入参可封装为统一的请求对象(如CryptoRequest,包含encryptContent和sign字段),出参封装为CryptoResponse,简化切面中的参数处理逻辑。
- 密钥管理:密钥应从配置文件(如application.yml)或密钥管理服务(如 KMS)读取,避免硬编码;生产环境中提议使用非对称加密(如 RSA)交换对称密钥(如 AES),提高安全性。
- 异常处理:在切面中增加统一的异常处理(如加密解密失败、签名验证失败等),返回标准化的错误响应。
- 性能优化:对加解密和签名算法进行性能测试,必要时使用线程池或缓存(如密钥缓存)优化性能。

四、安全注意事项
- 加密算法选择:优先使用国密算法(如 SM2、SM3、SM4)或国际标准算法(如 AES-256、RSA-2048+、SHA-256+),避免使用弱算法(如 DES、MD5、SHA-1)。
- 签名防篡改:签名应包含请求的关键参数(如时间戳、随机数 nonce),防止重放攻击和参数篡改。
- 传输安全:加解密仅处理数据本身的安全,传输层应使用 HTTPS 协议,防止数据在传输过程中被窃听或篡改。
- 密钥安全:密钥应定期轮换,且严格控制访问权限,避免密钥泄露。
通过以上实现,即可在 Spring Boot 中通过一个注解快速实现方法级别的加解密与签名功能,且具备良好的扩展性和可维护性。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
基于springboot一个注解实现加解密与签名