实现思路
- 自定义脱敏的注释,包含:脱敏实类和脱敏字段
- 使用拦截器在数据入库前加密
- 使用拦截器在数据出库时解密
- 可以使用mybatis的配置,配置两个插件,去引入自定义的拦截器,来处理数据加密和解密操作
数据脱敏演示
- 下载源代码,github源码链接
- 初始化sql
- 修改配置文件的数据库连接配置
- 运行com.ocean.angel.tool.controller.UserInfoControllerTest.save()方法,入库数据如下图:

- 运行com.ocean.angel.tool.controller.UserInfoControllerTest.get(),出库数据,截图如下:

使用指南
- 在返回的实体类上添加注解@SensitiveBean
- 在返回的实体类上的加密字段添加@SensitiveField注解
- 运行注解方法,去数据库查询查看插入的数据
关键代码
- mybatis配置
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="cacheEnabled" value="true" /> <!-- 全局映射器启用缓存 -->
<setting name="useGeneratedKeys" value="true" /> <!-- 允许 JDBC 支持自动生成主键 -->
<setting name="defaultExecutorType" value="REUSE" /> <!-- 配置默认的执行器 -->
<setting name="logImpl" value="SLF4J" /> <!-- 指定 MyBatis 所用日志的具体实现 -->
<!-- <setting name="mapUnderscoreToCamelCase" value="true"/> 驼峰式命名 -->
</settings>
<plugins>
<plugin interceptor="com.ocean.angel.tool.interceptor.DecryptInterceptor"></plugin>
<plugin interceptor="com.ocean.angel.tool.interceptor.EncryptInterceptor"></plugin>
</plugins>
</configuration>
- 数据加密拦截器
package com.ocean.angel.tool.interceptor;
import com.ocean.angel.tool.annotation.SensitiveBean;
import com.ocean.angel.tool.annotation.SensitiveField;
import com.ocean.angel.tool.util.AESUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.plugin.*;
import org.springframework.core.annotation.AnnotationUtils;
import java.lang.reflect.Field;
import java.sql.PreparedStatement;
import java.util.Objects;
import java.util.Properties;
@Slf4j
@Intercepts({
@Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class),
})
public class EncryptInterceptor implements Interceptor {
@Override
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);
// 取出实例
Object parameterObject = parameterField.get(parameterHandler);
if (parameterObject != null) {
Class<?> parameterObjectClass = parameterObject.getClass();
// 校验该实例的类是否被@SensitiveData所注解
SensitiveBean sensitiveData = AnnotationUtils.findAnnotation(parameterObjectClass, SensitiveBean.class);
if (Objects.nonNull(sensitiveData)) {
// 取出当前当前类所有字段,传入加密方法
Field[] declaredFields = parameterObjectClass.getDeclaredFields();
encrypt(declaredFields, parameterObject);
}
}
return invocation.proceed();
}
/**
* 切记配置,否则当前拦截器不会加入拦截器链
*/
@Override
public Object plugin(Object o) {
return Plugin.wrap(o, this);
}
// 自定义配置写入,没有自定义配置的可以直接置空此方法
@Override
public void setProperties(Properties properties) {
}
public <T> T encrypt(Field[] declaredFields, T paramsObject) throws IllegalAccessException {
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;
// 加密, 这里我使用自定义的AES加密工具
try {
field.set(paramsObject, AESUtil.encrypt(value));
} catch (IllegalArgumentException e) {
log.error("AES加密失败");
}
}
}
}
return paramsObject;
}
}
- 数据解密拦截器
package com.ocean.angel.tool.interceptor;
import cn.hutool.core.collection.CollUtil;
import com.ocean.angel.tool.annotation.SensitiveBean;
import com.ocean.angel.tool.annotation.SensitiveField;
import com.ocean.angel.tool.util.AESUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.plugin.*;
import org.springframework.core.annotation.AnnotationUtils;
import java.lang.reflect.Field;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Objects;
import java.util.Properties;
@Slf4j
@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 (!CollUtil.isEmpty(resultList) && needToDecrypt(resultList.get(0))) {
for (Object result : resultList) {
// 逐一解密
decrypt(result);
}
}
// 基于selectOne
} else {
if (needToDecrypt(resultObject)) {
decrypt(resultObject);
}
}
return resultObject;
}
private boolean needToDecrypt(Object object) {
if (ObjectUtils.isEmpty(object)) {
return false;
}
Class<?> objectClass = object.getClass();
SensitiveBean sensitiveDean = AnnotationUtils.findAnnotation(objectClass, SensitiveBean.class);
return Objects.nonNull(sensitiveDean);
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
public <T> T decrypt(T result) throws IllegalAccessException {
// 取出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;
// 对注解的字段进行逐一解密
try {
field.set(result, AESUtil.decrypt(value));
} catch (Exception e) {
log.error("AES解密失败");
}
}
}
}
return result;
}
}