mybatis基于拦截器对敏感数据加密
类注解
import java.lang.annotation.*;
/**
* BUG不找我
* 敏感信息类的注解
* @author huatao
* @date 2023/12/29 19:40
*/
@Inherited
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface SensitiveData {
}
敏感字段注解
import java.lang.annotation.*;
/**
* BUG不找我
* 注解敏感信息类中敏感字段的注解
* @author huatao
* @date 2023/12/29 19:41
*/
@Inherited
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface SensitiveField {
}
AES对称加密
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.symmetric.AES;
import system.dependent.data.encryptio.annotate.SensitiveField;
import java.lang.reflect.Field;
import java.util.Objects;
/**
* BUG不找我
* AES加密类
* @author huatao
* @date 2023/12/30 9:39
*/
public class AesUtils {
private final static String KEY="mybatis.interceptor.keys";//加解密key值
private final static AES aesUtils= SecureUtil.aes(KEY.getBytes());//加密对象
/**
* 加密
* @param declaredFields
* @param paramsObject
* @param <T>
* @return
* @throws Exception
*/
public static <T> T encrypt(Field[] declaredFields, T paramsObject) throws Exception {
for (Field field : declaredFields) {
//取出所有被EncryptDecryptField注解的字段
SensitiveField sensitiveField = field.getAnnotation(SensitiveField.class);
//判断是否为空
if (!Objects.isNull(sensitiveField)) {
//反射设置允许获取私有属性
field.setAccessible(true);
//获取对象属性
Object object = field.get(paramsObject);
//暂时只实现String类型的加密
if (object instanceof String) {
String value = (String) object;
//加密数据
byte[] encrypt = aesUtils.encrypt(value);
//加密 这里我使用自定义的AES加密工具
field.set(paramsObject, aesUtils.encryptHex(encrypt));
}
}
}
return paramsObject;
}
/**
* 解密
* @param result
* @param <T>
* @return
* @throws Exception
*/
public static <T> T decrypt(T result) throws Exception {
//取出resultType的类
Class<?> resultClass = result.getClass();
Field[] declaredFields = resultClass.getDeclaredFields();
for (Field field : declaredFields) {
//取出所有被EncryptDecryptField注解的字段
SensitiveField sensitiveField = field.getAnnotation(SensitiveField.class);
if (!Objects.isNull(sensitiveField)) {
field.setAccessible(true);
Object object = field.get(result);
//只支持String的解密
if (object instanceof String) {
String value = (String) object;
//对注解的字段进行逐一解密
//对于纯数字字符不解密
field.set(result, aesUtils.decryptStr(value));
}
}
}
return result;
}
}
解密类
/**
* BUG不找我
* 出参解密拦截器
* @author huatao
* @date 2023/12/30 9:02
*/
@Component
@Intercepts(
@Signature(type = ResultSetHandler.class,method = "handleResultSets",args = {Statement.class})
)
public class DecryptInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
//取出查询的结果
Object resultObject = invocation.proceed();
if (Objects.isNull(resultObject)) {
return null;
}
//基于selectList
if (resultObject instanceof ArrayList) {
ArrayList resultList = (ArrayList) resultObject;
if (!CollectionUtils.isEmpty(resultList) && needToDecrypt(resultList.get(0))) {
for (Object result : resultList) {
//逐一解密
AesUtils.decrypt(result);
}
}
//基于selectOne
} else {
if (needToDecrypt(resultObject)) {
AesUtils.decrypt(resultObject);
}
}
return resultObject;
}
private boolean needToDecrypt(Object object) {
Class<?> objectClass = object.getClass();
SensitiveData sensitiveData = AnnotationUtils.findAnnotation(objectClass, SensitiveData.class);
return Objects.nonNull(sensitiveData);
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}
加密类
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.plugin.*;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import system.dependent.data.encryptio.annotate.SensitiveData;
import system.dependent.data.encryptio.utils.AesUtils;
import java.lang.reflect.Field;
import java.sql.PreparedStatement;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
/**
* BUG不找我
* 入参加密拦截器
* @author huatao
* @date 2023/11/11 14:45
*/
@Component
@Intercepts({
@Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class),
})
public class EncryptInterceptor implements Interceptor {
private final AESManager aesManager;
@Autowired
public EncryptInterceptor(AESManager aesManager) {
this.aesManager = aesManager;
}
public Object intercept(Invocation invocation) throws Throwable {
//@Signature 指定了 type= parameterHandler 后,这里的 invocation.getTarget() 便是parameterHandler
//若指定ResultSetHandler ,这里则能强转为ResultSetHandler
ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();
// 获取参数对像,即 mapper 中 paramsType 的实例
Field parameterField = parameterHandler.getClass().getDeclaredField("parameterObject");
parameterField.setAccessible(true);
//取出实例,当parameterObject 是单独一个对象时
Object parameterObject = parameterField.get(parameterHandler);
if (parameterObject != null) {
Class<?> parameterObjectClass = parameterObject.getClass();
//校验该实例的类是否被@SensitiveData所注解
SensitiveData sensitiveData = AnnotationUtils.findAnnotation(parameterObjectClass, SensitiveData.class);
if (Objects.nonNull(sensitiveData)) {
//取出当前当前类所有字段,传入加密方法
Field[] declaredFields = parameterObjectClass.getDeclaredFields();
aesManager.encrypt(declaredFields, parameterObject);
}
}
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}
mybatis-plus新增和修改差异
mybatis入参加密以及查询解密到这里就结束了,但是mybatis和mybatis-plus有点差异。
我们一般写mybatis新增和修改时mapper层方法如下
import office.automation.bean.po.UserInfo ;
import office.automation.mapper.base.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/**
* BUG不找我
* 用户数据访问层
* @author huatao
* @date 2023/10/31 14:57
*/
@Mapper
public interface UserInfoMapper extends BaseMapper<AuthorityUserInfoPO> {
/**
* 新增用户
* @param userInfo
* @return
*/
int addUserInfo(UserInfo userInfo);
/**
* 修改用户
* @param userInfo
* @return
*/
int updateUserInfo(UserInfo userInfo);
}
mybatis-plus 底层封装的新增和修改如下
根据上面代码可以看出来myabtis-plus修改方法多了一个@Param注解,这个就是我们为什么新增数据时敏感数据可以加密成功,而修改却不行,下面是我们新增和修改使用mybatis-plus拦截器拦截时获取到的数据
新增
修改
从截图可以看出来,我们新增时获取到入参直接就是实体类,但是修改时获取到的是一个ParamMap,这是因为前面说过的mybatis-plus入参时有一个@Param注解 ,导致我们不能直接获取到我们具体的数据实体类,所以使用mybatis-plus时加密类需要修改成如下
mybatis-plus加密类
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.plugin.*;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import system.dependent.data.encryptio.annotate.SensitiveData;
import system.dependent.data.encryptio.utils.AesUtils;
import java.lang.reflect.Field;
import java.sql.PreparedStatement;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
/**
* BUG不找我
* 入参加密拦截器
* @author huatao
* @date 2023/12/30 9:17
*/
@Component
@Intercepts({@Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class),})
public class EncryptInterceptor implements Interceptor {
public Object intercept(Invocation invocation) throws Throwable {
//@Signature 指定了 type= parameterHandler 后,这里的 invocation.getTarget() 便是parameterHandler
//若指定ResultSetHandler ,这里则能强转为ResultSetHandler
ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();
// 获取参数对像,即 mapper 中 paramsType 的实例
Field parameterField = parameterHandler.getClass().getDeclaredField("parameterObject");
parameterField.setAccessible(true);
//取出实例,当parameterObject 是单独一个对象时
Object parameterObject = parameterField.get(parameterHandler);
if (parameterObject != null) {
Class<?> parameterObjectClass =null;
//判断入参是否为map类型
if(parameterObject instanceof MapperMethod.ParamMap ){
Map paramMap = (Map) parameterObject;
//判断key是否有et字符串
if(paramMap.containsKey("et")){
parameterObject=paramMap.get("et");
}
}
parameterObjectClass=parameterObject.getClass();
//校验该实例的类是否被@SensitiveData所注解
SensitiveData sensitiveData = AnnotationUtils.findAnnotation(parameterObjectClass, SensitiveData.class);
if (Objects.nonNull(sensitiveData)) {
//取出当前当前类所有字段,传入加密方法
Field[] declaredFields = parameterObjectClass.getDeclaredFields();
AesUtils.encrypt(declaredFields, parameterObject);
}
}
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}