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

阿里云教程3个月前发布
23 2 0

要在 Spring Boot 中通过一个注解实现加解密与签名功能,我们可以利用AOP(面向切面编程) 来拦截注解标注的方法,在方法执行前后处理加解密和签名逻辑。

一、核心思路

  1. 自定义注解:定义一个注解(如@CryptoSign),用于标记需要加解密和签名的方法。
  2. AOP 切面:编写切面类,拦截标注了@CryptoSign的方法,实现:入参解密:对请求参数进行解密处理。出参与密与签名:对响应结果进行加密,并生成签名(如 MD5、SHA256、RSA 等)。签名验证:对请求中的签名进行验证(可选,根据业务需求)。
  3. 加解密工具类:封装对称加密(如 AES)、非对称加密(如 RSA)和签名算法(如 HmacSHA256、RSA 签名)的工具方法。

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

二、具体实现

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;
    }
}

三、关键扩展点

  1. 算法扩展:可在CryptoUtils中扩展 RSA 非对称加密、RSA 签名、SM4(国密)等算法,只需在注解和切面中增加对应算法的判断逻辑。
  2. 参数格式优化:实际项目中,入参可封装为统一的请求对象(如CryptoRequest,包含encryptContent和sign字段),出参封装为CryptoResponse,简化切面中的参数处理逻辑。
  3. 密钥管理:密钥应从配置文件(如application.yml)或密钥管理服务(如 KMS)读取,避免硬编码;生产环境中提议使用非对称加密(如 RSA)交换对称密钥(如 AES),提高安全性。
  4. 异常处理:在切面中增加统一的异常处理(如加密解密失败、签名验证失败等),返回标准化的错误响应。
  5. 性能优化:对加解密和签名算法进行性能测试,必要时使用线程池或缓存(如密钥缓存)优化性能。

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

四、安全注意事项

  1. 加密算法选择:优先使用国密算法(如 SM2、SM3、SM4)或国际标准算法(如 AES-256、RSA-2048+、SHA-256+),避免使用弱算法(如 DES、MD5、SHA-1)。
  2. 签名防篡改:签名应包含请求的关键参数(如时间戳、随机数 nonce),防止重放攻击和参数篡改。
  3. 传输安全:加解密仅处理数据本身的安全,传输层应使用 HTTPS 协议,防止数据在传输过程中被窃听或篡改。
  4. 密钥安全:密钥应定期轮换,且严格控制访问权限,避免密钥泄露。

通过以上实现,即可在 Spring Boot 中通过一个注解快速实现方法级别的加解密与签名功能,且具备良好的扩展性和可维护性。

© 版权声明

相关文章

2 条评论

  • 可爱头像bot
    可爱头像bot 投稿者

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

    回复