一、前言
在实际项目开发中,可能会涉及到一些敏感信息,那么我们就需要对这些敏感信息进行加密处理,
也就是脱敏,比如像手机号、身份证号等信息。如果我们只是在接口返回后再去做替换处理,则代码会显得非常冗余,那么实际可以通过注解的方式实现数据脱敏。
二、具体实现
1.定义一个标记于方法上的注解。
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD) //METHOD 说明该注解只能用在方法上
@Retention(RetentionPolicy.RUNTIME) //RUNTIME 说明该注解在运行时生效
public @interface DataEncryption {
}
2.定义一个数据解密的注解。
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD) //METHOD 说明该注解只能用在方法上
@Retention(RetentionPolicy.RUNTIME) //RUNTIME 说明该注解在运行时生效
public @interface DataDecryption {
}
3.定义一个在字段上,且有值的注解。
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD) //FIELD 说明该注解只能用在字段上
@Retention(RetentionPolicy.RUNTIME) //RUNTIME 说明该注解在运行时生效
public @interface EncryptField {
//脱敏枚举值
DesensitizationEnum value();
}
4.定义一个在字段上,且有值的注解。
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD) //FIELD 说明该注解只能用在字段上
@Retention(RetentionPolicy.RUNTIME) //RUNTIME 说明该注解在运行时生效
public @interface DecryptField {
//脱敏枚举值
DesensitizedUtil.DesensitizedType value();
}
5.定义用户返回的实体类。
import com.test.annotation.DecryptField;
import com.test.enums.DesensitizationEnum;
import com.test.utils.AesUtil;
import java.lang.reflect.Field;
import lombok.Data;
// 用户信息返回实体类
@Data
public class UserResVo {
@DecryptField(DesensitizedUtil.DesensitizedType.CHINESE_NAME)
@EncryptField
private String name;
@EncryptField
@EncryptField(DesensitizedUtil.DesensitizedType.ID_CARD)
private String idCard;
@EncryptField
@EncryptField(DesensitizedUtil.DesensitizedType.MOBILE_PHONE)
private String phone;
@EncryptField
@EncryptField(DesensitizedUtil.DesensitizedType.EMAIL)
private String email;
}
6.定义AOP注解实现
import cn.hutool.crypto.digest.DigestUtil;
import com.google.gson.Gson;
import lombok.extern.slf4j.Slf4j;
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.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Field;
import java.util.Objects;
@Aspect
@Component
@Slf4j
public class SecretAopAspect {
@Pointcut("@annotation(com.secret.annotation.DataEncryption)")
public void encryptAopCut() {
}
@Pointcut("@annotation(com.secret.annotation.DataDecryption)")
public void decryptAopCut() {
}
/**
* @method encryptMethodAop
* @desc 加密方法
* @Param: joinPoint
*/
@Around("encryptAopCut()")
public Object encryptMethodAop(ProceedingJoinPoint joinPoint) {
Object responseObj = null;
try {
responseObj = joinPoint.proceed();
this.handleEncrypt(responseObj);
//md5加密,在相应里面设置签名值
String md5Data = DigestUtil.md5Hex(new Gson().toJson(responseObj));
HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
response.setHeader("md5",md5Data);
} catch (Throwable throwable) {
throwable.printStackTrace();
this.log.error("encryptMethodAop处理出现异常{}", throwable);
}
return responseObj;
}
/**
* @method decryptMethodAop
* @desc 解密方法
* @Param: joinPoint
*/
@Around("decryptAopCut()")
public Object decryptMethodAop(ProceedingJoinPoint joinPoint) {
Object responseObj = null;
try {
responseObj = joinPoint.getArgs()[0];
//throw new RuntimeException("md5校验失败");
this.handleDecrypt(responseObj);
//生成md5签名
String md5 = DigestUtil.md5Hex(new Gson().toJson(responseObj));
System.out.println(md5);
//从请求头获取前端传过来的签名
String origianlMd5 = "";
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
origianlMd5 = request.getHeader("md5");
//比对签名
if(md5.equals(origianlMd5)){//方便调试,不比对前端签名
responseObj = joinPoint.proceed();
}else{
throw new Exception("参数的md5校验不同,可能存在篡改行为,请检查!");
}
} catch (Throwable throwable) {
throwable.printStackTrace();
log.error("decryptMethodAop处理出现异常{}", throwable);
}
return responseObj;
}
/**
* @method handleEncrypt
* @desc 对实体么个属性进行加密
* @Param: requestObj
* @return void
*/
private void handleEncrypt(Object requestObj) throws Exception {
if (!Objects.isNull(requestObj)) {
Field[] fields = requestObj.getClass().getDeclaredFields();
Field[] fieldsCopy = fields;
int fieldLength = fields.length;
for(int i = 0; i < fieldLength; ++i) {
Field field = fieldsCopy[i];
boolean hasSecureField = field.isAnnotationPresent(EncryptField.class);
if (hasSecureField) {
field.setAccessible(true);
String plaintextValue = (String)field.get(requestObj);
String cipherText = AesUtil.encrypt(plaintextValue);
field.set(requestObj, cipherText);
}
}
}
}
/**
* @method handleDecrypt
* @desc 对实体么个属性进行解密
* @Param: responseObj
* @return
*/
private Object handleDecrypt(Object responseObj) throws Exception {
if (Objects.isNull(responseObj)) {
return null;
} else {
Field[] fields = responseObj.getClass().getDeclaredFields();
Field[] fieldsCopy = fields;
int fieldLength = fields.length;
for(int i = 0; i < fieldLength; ++i) {
Field field = fieldsCopy[i];
boolean hasSecureField = field.isAnnotationPresent(DecryptField.class);
if (hasSecureField) {
DecryptField decryptField = field.getAnnotation(DecryptField.class);
field.setAccessible(true);
String encryptValue = (String)field.get(responseObj);
encryptValue = AesUtil.decrypt(encryptValue,decryptField.getValue());
field.set(responseObj, encryptValue);
}
}
return responseObj;
}
}
}
7.加解密工具类
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.DesensitizedUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.symmetric.AES;
public class AesUtil {
// 默认16位 或 128 256位
public static String AES_KEY = "EN#qerdfdshbd110";
public static AES aes = SecureUtil.aes(AES_KEY.getBytes());
public static String encrypt(Object obj) {
return aes.encryptHex((String) obj);
}
public static String decrypt(Object obj, DesensitizedUtil.DesensitizedType desensitizedType) {
// 解密
String decrypt = decrypt(obj);
// 脱敏
return DesensitizedUtil.desensitized(decrypt, desensitizedType);
}
public static String decrypt(Object obj) {
return aes.decryptStr((String) obj, CharsetUtil.CHARSET_UTF_8);
}
}
8.接着就可以测试了。