目录
1、对称加解密工具类
package com.demo.modules.desensitization;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.crypto.SecureUtil;
import com.demo.modules.desensitization.annotation.EncryptedField;
import com.demo.modules.desensitization.annotation.EncryptedTable;
import org.springframework.core.annotation.AnnotationUtils;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
/**
* @title 对称加解密工具类
* @description 主要用于mybatis拦截器中加解密字段
* @author: Wanglei
* @create 2024/4/12 0012 18:39:40
*/
public class EncryptDecryptUtils {
private static final byte[] KEYS = "Apa987^%$321c2mp".getBytes(StandardCharsets.UTF_8);
/**
* 对指定的对象进行加密处理。
* 该方法首先获取对象的类信息,然后检查该类是否被@EncryptedTable注解标记。如果被标记,
* 则遍历该类的所有字段,对这些需要加密的字段进行加密处理。
*
* @param parameterObject 需要进行加密处理的对象。该对象的类必须被@EncryptedTable注解标记。
* @throws IllegalAccessException 如果在访问字段时发生访问权限问题,则抛出此异常。
*/
public static void doEncrypt(Object parameterObject) throws IllegalAccessException{
if(ObjectUtil.isEmpty(parameterObject)){
return;
}
// 获取传入对象的类信息
Class<?> parameterObjectClass = parameterObject.getClass();
// 尝试查找该类上的@EncryptedTable注解
EncryptedTable encryptDecryptClass = AnnotationUtils.findAnnotation(parameterObjectClass, EncryptedTable.class);
if (Objects.nonNull(encryptDecryptClass)){
// 获取类中所有声明的字段
Field[] declaredFields = parameterObjectClass.getDeclaredFields();
// 对所有字段进行加密处理
encrypt(declaredFields, parameterObject);
}
}
/**
* 多field加密方法
*
* @param declaredFields
* @param parameterObject
* @param <T>
* @return
* @throws IllegalAccessException
*/
public static <T> T encrypt(Field[] declaredFields, T parameterObject) throws IllegalAccessException {
for (Field field : declaredFields) {
EncryptedField annotation = field.getAnnotation(EncryptedField.class);
if (Objects.isNull(annotation)) {
continue;
}
encrypt(field, parameterObject);
}
return parameterObject;
}
/**
* 单个field加密方法
*
* @param field
* @param parameterObject
* @param <T>
* @return
* @throws IllegalAccessException
*/
public static <T> T encrypt(Field field, T parameterObject) throws IllegalAccessException {
field.setAccessible(true);
Object object = field.get(parameterObject);
if (object instanceof BigDecimal) {
BigDecimal value = (BigDecimal) object;
long longValue = value.movePointRight(4).subtract(BigDecimal.valueOf(Integer.MAX_VALUE >> 3)).longValue();
field.set(parameterObject, BigDecimal.valueOf(longValue));
} else if (object instanceof Integer) {
} else if (object instanceof Long) {
} else if (object instanceof String) {
//定制String类型的加密算法
String value = (String) object;
field.set(parameterObject, SecureUtil.aes(KEYS).encryptHex(value));
}
return parameterObject;
}
/**
* 解密方法
*
* @param result
* @param <T>
* @return
* @throws IllegalAccessException
*/
public static <T> T decrypt(T result) throws IllegalAccessException {
Class<?> parameterObjectClass = result.getClass();
Field[] declaredFields = parameterObjectClass.getDeclaredFields();
decrypt(declaredFields, result);
return result;
}
/**
* 多个field解密方法
*
* @param declaredFields
* @param result
* @throws IllegalAccessException
*/
public static void decrypt(Field[] declaredFields, Object result) throws IllegalAccessException {
for (Field field : declaredFields) {
EncryptedField annotation = field.getAnnotation(EncryptedField.class);
if (Objects.isNull(annotation)) {
continue;
}
decrypt(field, result);
}
}
/**
* 单个field解密方法
*
* @param field
* @param result
* @throws IllegalAccessException
*/
public static void decrypt(Field field, Object result) throws IllegalAccessException {
field.setAccessible(true);
Object object = field.get(result);
if (object instanceof BigDecimal) {
BigDecimal value = (BigDecimal) object;
double doubleValue = value.add(BigDecimal.valueOf(Integer.MAX_VALUE >> 3)).movePointLeft(4).doubleValue();
field.set(result, BigDecimal.valueOf(doubleValue));
} else if (object instanceof Integer) {
} else if (object instanceof Long) {
} else if (object instanceof String) {
//定制String类型的解密算法
String value = (String) object;
//对注解的字段进行逐一解密
String decryptStr = value;
// 使用try-catch捕获异常,处理数据库中未加密的数据,直接返回未加密数据
try{
decryptStr = SecureUtil.aes(KEYS).decryptStr(value);
}catch (Exception e){
e.printStackTrace();
}
field.set(result, decryptStr);
}
}
}
2、拦截器
2.1、解密拦截器
package com.demo.modules.desensitization.interceptor;
import com.demo.modules.config.Properties;
import com.demo.modules.desensitization.EncryptDecryptUtils;
import com.demo.modules.desensitization.annotation.EncryptedTable;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.plugin.*;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Objects;
/**
* @title 解密拦截器
* @description 解密拦截器
* @author: Wanglei
* @create 2024/4/12 0012 17:40:02
*/
@Intercepts({
@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})
})
@Component
public class DecryptInterceptor implements Interceptor {
@Resource
private Properties properties;
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object resultObject = invocation.proceed();
if (Objects.isNull(resultObject)) {
return null;
}
// 未开启脱敏直接返回结果
if(!properties.getDesensitizationEnabled()){
return resultObject;
}
if (resultObject instanceof ArrayList) {
//基于selectList
ArrayList resultList = (ArrayList) resultObject;
if (!resultList.isEmpty() && needToDecrypt(resultList.get(0))) {
for (Object result : resultList) {
//逐一解密
EncryptDecryptUtils.decrypt(result);
}
}
} else if (needToDecrypt(resultObject)) {
//基于selectOne
EncryptDecryptUtils.decrypt(resultObject);
}
return resultObject;
}
/**
* 校验该实例的类是否被@EncryptedTable所注解
*/
private boolean needToDecrypt(Object object) {
Class<?> objectClass = object.getClass();
EncryptedTable sensitiveData = AnnotationUtils.findAnnotation(objectClass, EncryptedTable.class);
return Objects.nonNull(sensitiveData);
}
@Override
public Object plugin(Object o) {
return Plugin.wrap(o, this);
}
}
2.2、加密拦截器
package com.demo.modules.desensitization.interceptor;
import com.demo.modules.config.Properties;
import com.demo.modules.desensitization.EncryptDecryptUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.sql.PreparedStatement;
import java.util.*;
/**
* @title 加密拦截器
* @description 加密拦截器
* @author: Wanglei
* @create 2024/4/12 0012 16:23:42
*/
@Intercepts({
@Signature(type = ParameterHandler.class,method = "setParameters",args = PreparedStatement.class)
})
@Slf4j
@Component
public class EncryptInterceptor implements Interceptor {
@Resource
private Properties properties;
/** 获取参数对象方法名称*/
private final static String getParameterObjectMethod = "getParameterObject";
@Override
public Object intercept(Invocation invocation) throws Throwable {
//拦截 ParameterHandler 的 setParameters 方法 动态设置参数
if (invocation.getTarget() instanceof ParameterHandler && properties.getDesensitizationEnabled()) {
ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();
// 反射获取 参数方法
Class<? extends ParameterHandler> aClass = parameterHandler.getClass();
Method getParameterObject = aClass.getDeclaredMethod(getParameterObjectMethod);
Object parameterObject = getParameterObject.invoke(parameterHandler);
if (Objects.nonNull(parameterObject)){
//mybatis-plus 框架默认多参数
if (parameterObject instanceof MapperMethod.ParamMap){
MapperMethod.ParamMap paramMap = (MapperMethod.ParamMap) parameterObject;
//避免相同的key重复执行,使用set对象记录堆栈地址
HashSet<Integer> stackSet = new HashSet<>();
for (Object key : paramMap.keySet()) {
//需要使用hashcode判断,equals()方法可能被重写。
if (stackSet.add(System.identityHashCode(paramMap.get(key)))) {
EncryptDecryptUtils.doEncrypt(paramMap.get(key));
}
}
} else {
EncryptDecryptUtils.doEncrypt(parameterObject);
}
}
}
return invocation.proceed();
}
}
3、注解
3.1、EncryptedField
package com.demo.modules.desensitization.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @title 需要加解密的字段用这个注解
* @description TODO description class purposex
* @author: Wanglei
* @create 2024/4/12 0012 16:22:30
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface EncryptedField {
}
3.2、EncryptedTable
package com.demo.modules.desensitization.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @title 需要加解密的实体类用这个注解
* @description TODO description class purposex
* @author: Wanglei
* @create 2024/4/12 0012 16:22:10
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface EncryptedTable {
}
4、使用方法
在需要加解密的实体类头部添加注解@EncryptedTable,同时在需要加解密的字段上添加@EncryptedField注解即可。