基于springboot一个注解实现加解密与签名

2026年01月19日/ 浏览 9

要在 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 中通过一个注解快速实现方法级别的加解密与签名功能,且具备良好的扩展性和可维护性。

picture loss