AOP实现接口入参、出参加解密功能
借助Spring的AOP和注解,实现对业务接口请求参数和响应参数的加密、解密功能
一、后端部分
1、注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Secret
{
// 参数类(用来传递加密数据,只有方法参数中有此类或此类的子类才会执行加解密)
Class value();
// 参数类中传递加密数据的属性名,默认encryptStr
String encryptStrName() default "encryptStr";
}
2、切面类
import com.alibaba.fastjson2.JSON;
import com.ruoyi.common.annotation.Secret;
import com.ruoyi.common.core.domain.AjaxResult;
import org.apache.commons.lang3.StringUtils;
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.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
/**
* 接口入参、回参加解密
*
* @author 逆行
* @date 2024/02/20 15:22
**/
@Aspect
@Component
public class SecretAspect
{
// 是否进行加密解密,通过配置文件注入(不配置默认为true)
@Value("${isSecret:true}")
boolean isSecret;
// 定义切点,使用了@Secret注解的类 或 使用了@Secret注解的方法
@Pointcut("@within(com.ruoyi.common.annotation.Secret) || @annotation(com.ruoyi.common.annotation.Secret)")
public void pointcut()
{
}
// 环绕切面
@Around("pointcut()")
public Object around(ProceedingJoinPoint point)
{
AjaxResult result = null;
// 获取被代理方法参数
Object[] args = point.getArgs();
// 获取被代理对象
Object target = point.getTarget();
// 获取通知签名
MethodSignature signature = (MethodSignature) point.getSignature();
try
{
// 获取被代理方法
Method pointMethod = target.getClass().getMethod(signature.getName(), signature.getParameterTypes());
// 获取被代理方法上面的注解@Secret
Secret secret = pointMethod.getAnnotation(Secret.class);
// 被代理方法上没有,则说明@Secret注解在被代理类上
if (secret == null)
{
secret = target.getClass().getAnnotation(Secret.class);
}
if (secret != null)
{
// 获取注解上声明的加解密类
Class clazz = secret.value();
// 获取注解上声明的加密参数名
String encryptStrName = secret.encryptStrName();
for (int i = 0; i < args.length; i++)
{
// 如果是clazz类型则说明使用了加密字符串encryptStr传递的加密参数
if (clazz.isInstance(args[i]))
{
Object cast = clazz.cast(args[i]); //将args[i]转换为clazz表示的类对象
// 通过反射,执行getEncryptParam()方法,获取加密数据
Method method = clazz.getMethod(getMethodName(encryptStrName));
// 执行方法,获取加密数据
String encryptStr = (String) method.invoke(cast);
// 加密字符串是否为空
if (StringUtils.isNotBlank(encryptStr))
{
// 解密
String json = DesUtils.decrypt(encryptStr);
// 转换vo
args[i] = JSON.parseObject(json, (Type) args[i].getClass());
}
}
// 其他类型,比如基本数据类型、包装类型就不使用加密解密了
}
}
// 执行请求
result = (AjaxResult) point.proceed(args);
// 判断配置是否需要返回加密
if (isSecret)
{
// 获取返回值json字符串
String jsonString = JSON.toJSONString(result.get("data"));
// 加密
String s = DesUtils.encrypt(jsonString);
result.put("data", s);
}
} catch (Throwable e)
{
throw new RuntimeException(e);
}
return result;
}
/**
* 转化方法名
*
* @param name
* @return
*/
private String getMethodName(String name)
{
String first = name.substring(0, 1);
String last = name.substring(1);
first = StringUtils.upperCase(first);
return "get" + first + last;
}
}
3、加解密工具类
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import java.security.SecureRandom;
import java.util.Base64;
/**
* 加解密工具类
*
* @Author: 逆行
* @DATE: 2024/2/27
*/
public class DesUtils
{
private final static String ALGORITHM = "ALGORITHM";// 算法
private final static String CHARSET = "UTF-8";// 字符集编码
private final static String DEFAULT_KEY = "www.gzjxlcit.com";// 秘钥
/**
* @Title 使用 默认key 加密
* @Author 逆行
* @Date 2020/5/21 17:02
* @versioin V1.0
**/
public static String encrypt(String data) throws Exception
{
byte[] bt = encrypt(data.getBytes(CHARSET), DEFAULT_KEY.getBytes(CHARSET));
return Base64.getEncoder().encodeToString(bt);
}
/**
* @Title 使用 默认key 解密
* @Author 逆行
* @Date 2020/5/21 17:02
* @versioin V1.0
**/
public static String decrypt(String data) throws Exception
{
if (data == null)
{
return null;
}
byte[] buf = Base64.getDecoder().decode(data);
byte[] bt = decrypt(buf, DEFAULT_KEY.getBytes(CHARSET));
return new String(bt, CHARSET);
}
/**
* @Title 根据键值进行加密
* @Author 逆行
* @Date 2020/5/21 17:03
* @versioin V1.0
**/
public static String encrypt(String data, String key) throws Exception
{
byte[] bt = encrypt(data.getBytes(CHARSET), key.getBytes(CHARSET));
return Base64.getEncoder().encodeToString(bt);
}
/**
* @Title 根据键值进行解密
* @Author 逆行
* @Date 2020/5/21 17:03
* @versioin V1.0
**/
public static String decrypt(String data, String key) throws Exception
{
if (data == null)
{
return null;
}
byte[] buf = Base64.getDecoder().decode(data);
byte[] bt = decrypt(buf, key.getBytes(CHARSET));
return new String(bt, CHARSET);
}
/**
* @Title 根据键值进行加密
* @Author 逆行
* @Date 2020/5/21 17:04
* @versioin V1.0
**/
private static byte[] encrypt(byte[] data, byte[] key) throws Exception
{
// 生成一个可信任的随机数源
SecureRandom sr = new SecureRandom();
// 从原始密钥数据创建DESKeySpec对象
DESKeySpec dks = new DESKeySpec(key);
// 创建一个密钥工厂,然后用它把DESKeySpec转换成SecretKey对象
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(ALGORITHM);
SecretKey securekey = keyFactory.generateSecret(dks);
// Cipher对象实际完成加密操作
Cipher cipher = Cipher.getInstance(ALGORITHM);
// 用密钥初始化Cipher对象
cipher.init(Cipher.ENCRYPT_MODE, securekey, sr);
return cipher.doFinal(data);
}
/**
* @Title 根据键值进行解密
* @Author 逆行
* @Date 2020/5/21 17:04
* @versioin V1.0
**/
private static byte[] decrypt(byte[] data, byte[] key) throws Exception
{
// 生成一个可信任的随机数源
SecureRandom sr = new SecureRandom();
// 从原始密钥数据创建DESKeySpec对象
DESKeySpec dks = new DESKeySpec(key);
// 创建一个密钥工厂,然后用它把DESKeySpec转换成SecretKey对象
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(ALGORITHM);
SecretKey securekey = keyFactory.generateSecret(dks);
// Cipher对象实际完成解密操作
Cipher cipher = Cipher.getInstance(ALGORITHM);
// 用密钥初始化Cipher对象
cipher.init(Cipher.DECRYPT_MODE, securekey, sr);
return cipher.doFinal(data);
}
}
4、使用示例
@Encrypt(value = OuterChainCheckVo.class, encryptStrName = "encryptParam")
@PostMapping("/access/check")
public AjaxResult check(@RequestBody OuterChainCheckVo vo)
{
}
// OuterChainCheckVo对象中必须包含一个与@Encrypt注解中value属性值同名的属性,如本例中OuterChainCheckVo对象需要包含encryptParam属性
二、前端部分
1、依赖
import CryptoJS from 'crypto-js';
2、加解密方法
// 参数加密
encrypt(text) {
var keyHex = CryptoJS.enc.Utf8.parse(key);
var encrypted = CryptoJS.DES.encrypt(text, keyHex, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
});
return encrypted.toString();
},
// 参数解密
decrypt(ciphertext) {
var keyHex = CryptoJS.enc.Utf8.parse(key);
const bytes = CryptoJS.DES.decrypt(ciphertext, keyHex, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
});
const originalText = bytes.toString(CryptoJS.enc.Utf8);
return originalText;
}
3、接口传参示例
{
encryptParam:"加密后的请求参数对象JSON字符串"
}