需求:
根据数据安全需求,需要把统一返回类中的data中的数据进行统一加密,然后由前端进行解密并把参数渲染到页面。
准备工作:
因为项目中很多接口都需要进行返回值数据加密,则考虑使用自定义注解+aop 切面技术实现。
只是对数据进行简单加密所以考虑使用des 对称加密,需要前端人员与后台开发人员商量好密钥即可满足需求。
自定义加密注解
import java.lang.annotation.*;
/**
* 返回参数加密注解
*/
// 可以作用在类上和方法上
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DESRespondSecret {
boolean validated() default true;
}
切面实现类
import com.alibaba.fastjson.JSON;
import com.test.apitest.annotation.DESRespondSecret;
import com.test.apitest.utils.EncryptUtil;
import com.test.apitest.vo.BaseVo;
import org.apache.commons.collections.CollectionUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.*;
@Component
@Aspect
//规定AOP 的执行顺序
//@Order(1) 多个aop 时可通过该注解来确定执行顺序
public class DESResponseSecretAspect {
// 定义切点,使用了@DESRespondSecret注解的类 或 使用了@DESRespondSecret注解的方法
/**
* 切点,方法上有注解或者类上有注解
* 拦截类或者是方法上标注注解的方法
*/
@Pointcut("@within(com.test.apitest.annotation.DESRespondSecret) || @annotation(com.test.apitest.annotation.DESRespondSecret)")
public void pointCut(){}
@Around("pointCut()")
public Object after(ProceedingJoinPoint joinPoint) throws Throwable {
Object rvt = joinPoint.proceed(joinPoint.getArgs());
return rvt;
}
/**
* 与After的区别在于AfterReturning只有在方法执行成功的之后才会被织入,如果After和
* AfterReturning同时存在于一个文件中,谁写在前面谁先运行
* @param joinpoint
*/
@AfterReturning(pointcut="execution(public * com.test.apitest.controller.*.*(..))", returning="rvt")
public Object log(JoinPoint joinpoint, Object rvt) {
// 获取被代理对象
Object target = joinpoint.getTarget();
// 获取通知签名 获取方法方法上的DESRespondSecret注解
MethodSignature signature = (MethodSignature) joinpoint.getSignature();
try {
// 获取被代理方法
Method pointMethod = target.getClass().getMethod(signature.getName(), signature.getParameterTypes());
// 获取被代理方法上面的注解@DESRespondSecret
DESRespondSecret secret = pointMethod.getAnnotation(DESRespondSecret.class);
// 被代理方法上没有,则说明@DESRespondSecret注解在被代理类上
if (secret == null) {
secret = target.getClass().getAnnotation(DESRespondSecret.class);
}
//类上也没有 直接返回
if(secret == null){
return rvt;
}
// 如果有,并且值为false,则不进行加密
if (secret != null && !secret.validated()) {
return rvt;
}else {
BaseVo baseVo1 = (BaseVo) rvt;
// 获取返回值json字符串
Object data = baseVo1.getData();
if(null != data){
String jsonString = JSON.toJSONString(data);
// 加密
String s = EncryptUtil.encrypt(jsonString);
baseVo1.setData(s);
}
return baseVo1;
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return rvt;
}
}
BaseVo类 公共返回实体类
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* @ClassName: BaseVo
* @Description: 基础视图对象
* @author:
* @date:
*/
@Data
@ApiModel(value = "基础视图对象")
public class BaseVo<T> implements Serializable{
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "提示码")
private String code;
@ApiModelProperty(value = "提示内容")
private String message;
@ApiModelProperty(value = "数据")
private Object data;
@ApiModelProperty(value = "主链路id")
protected String transactionId;
@ApiModelProperty(value = "相关链路id")
protected String spanId;
@ApiModelProperty(value = "服务信息")
protected String serverInfo;
public BaseVo<T> setCode(String code) {
this.code = code;
return this;
}
public BaseVo<T> setMessage(String message) {
this.message = message;
return this;
}
public BaseVo<T> setData(Object data) {
this.data = data;
return this;
}
}
EncryptUtil des对称加密算法
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import java.security.SecureRandom;
/**
* des 对称加密算法
*/
public class EncryptUtil {
public static String secretKeyB = "aa.test.business.apitest@2023";
private final static String DES = "DES";
//加密
public static String encrypt(String data) {
byte[] bt;
try {
bt = encrypt(data.getBytes(), secretKeyB.getBytes());
//加密
String strs = Base64.getEncoder().encodeToString(bt);
return strs;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
//解密
public static String decrypt(String data) {
if (data == null)
return null;
byte[] buf;
byte[] bt;
try {
//解密
buf = Base64.getDecoder().decode(data);
bt = decrypt(buf, secretKeyB.getBytes());
return new String(bt);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
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(DES);
SecretKey securekey = keyFactory.generateSecret(dks);
// Cipher对象实际完成加密操作
Cipher cipher = Cipher.getInstance(DES);
// 用密钥初始化Cipher对象
cipher.init(Cipher.ENCRYPT_MODE, securekey, sr);
return cipher.doFinal(data);
}
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(DES);
SecretKey securekey = keyFactory.generateSecret(dks);
// Cipher对象实际完成解密操作
Cipher cipher = Cipher.getInstance(DES);
// 用密钥初始化Cipher对象
cipher.init(Cipher.DECRYPT_MODE, securekey, sr);
return cipher.doFinal(data);
}
}
使用方法
在类上添加注解,整个类中的方法返回值都会加密:
在方法上添加注解,只有该方法的返回值加密:
如果在类上添加了注解,在该类中的方法上添加了@DESRespondSecret(validated = false) 则该方法的返回值则不会被加密