
集成mybatis-plus,使用mybatis拦截器方式做数据库字段脱敏处理
数据库字段加解密处理,字段脱敏处理
·
参考文章:基于MyBatis-Plus数据库存取字段加解密解法两种_数据库_虫叽叽-华为开发者空间
一、思路分析
最终实现的效果:新增数据的时候数据库表字段存储密文,查询的时候展示明文;采用SM4对称加密方式进行加解密,因为SM4可逆。
首先,mybatis的原理如下图:
由图可见,ParameterHandler是设置编译参数,最后保存到数据库中,ResultSetHandler是用来处理最终查询结果。
二、具体实现方法
1、自定义注解
import java.lang.annotation.*;
/**
* 自定义类注解
*/
@Documented
@Inherited
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptDecryptClass {
}
/**
* 自定义字段注解
*/
@Documented
@Inherited
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptDecryptField {
}
2、引用注解位置
注解需要放在映射数据库表的实体类中
@Data
@TableName(value = "tb_user", autoResultMap = true)
@EncryptDecryptClass
public class TbUser {
/**
* 主键
*/
@TableId(type = IdType.ASSIGN_ID)
private String id;
/**
* 用户名
*/
@EncryptDecryptField
@TableField(value = "username")
private String username;
/**
* 用户手机号码
*/
@TableField("role")
private String role;
}
注意:autoResultMap = true是必须存在的
3、设置拦截器
/**
* 设置入参拦截器(ParameterHandler)
*/
@Intercepts({
@Signature(type = ParameterHandler.class,method = "setParameters",args = PreparedStatement.class)
})
@ConditionalOnProperty(value = "domain.encrypt",havingValue = "true")
@Slf4j
public class MybatisParameterIntercept implements Interceptor {
@Autowired
private LocalPathConfig localPathConfig;
@Override
public Object intercept(Invocation invocation) throws Throwable {
//拦截 ParameterHandler 的 setParameters 方法 动态设置参数
if (invocation.getTarget() instanceof ParameterHandler) {
//注意ParameterHandler中的参数类型是实体类还是Map
ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();
Object parameterObject = parameterHandler.getParameterObject();
if (parameterObject instanceof Map){
Map<String,Object> paramMap = (Map<String, Object>) parameterObject;
// 遍历 ParamMap 以找到所有实体类的参数
for (Map.Entry<String, Object> entry : paramMap.entrySet()) {
//此处的user对应到mapper中注解Param的定义
if (entry.getKey().equals("user")) {
Object paramValue = entry.getValue();
// 检查参数值是否为实体类实例,TbUser是自己定义的实体类名
if (paramValue instanceof TbUser ) {
Class<?> parameterObjectClass = paramValue.getClass();
EncryptDecryptClass encryptDecryptClass = parameterObjectClass.getAnnotation(EncryptDecryptClass.class);
if (encryptDecryptClass != null) {
Field[] declaredFields = parameterObjectClass.getDeclaredFields();
final Object encrypt = EncryptDecryptUtils.encrypt(declaredFields, paramValue,localPathConfig.getEncDecrySm4Key());
}
}
}
}
}else {
if (Objects.nonNull(parameterObject)){
Class<?> parameterObjectClass = parameterObject.getClass();
EncryptDecryptClass encryptDecryptClass = AnnotationUtils.findAnnotation(parameterObjectClass, EncryptDecryptClass.class);
if (Objects.nonNull(encryptDecryptClass)){
Field[] declaredFields = parameterObjectClass.getDeclaredFields();
final Object encrypt = EncryptDecryptUtils.encrypt(declaredFields, parameterObject,localPathConfig.getEncDecrySm4Key());
}
}
}
}
return invocation.proceed();
}
}
/**
* 出参拦截器(ResultSetHandler)
*/@Intercepts({
@Signature(type = ResultSetHandler.class,method = "handleResultSets",args = Statement.class)
})
@ConditionalOnProperty(value = "domain.encrypt",havingValue = "true")
@Component
@Slf4j
public class MybatisResultSetIntercept implements Interceptor {
@Autowired
private LocalPathConfig localPathConfig;
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object result = invocation.proceed();
if (Objects.isNull(result)){
return null;
}
if (result instanceof ArrayList) {
ArrayList resultList = (ArrayList) result;
if (CollectionUtils.isNotEmpty(resultList) && needToDecrypt(resultList.get(0))){
for (int i = 0; i < resultList.size(); i++) {
EncryptDecryptUtils.decrypt(resultList.get(i),localPathConfig.getEncDecrySm4Key());
}
}
}else {
if (needToDecrypt(result)){
EncryptDecryptUtils.decrypt(result,localPathConfig.getEncDecrySm4Key());
}
}
return result;
}
private boolean needToDecrypt(Object object){
Class<?> objectClass = object.getClass();
EncryptDecryptClass encryptDecryptClass = AnnotationUtils.findAnnotation(objectClass, EncryptDecryptClass.class);
if (Objects.nonNull(encryptDecryptClass)){
return true;
}
return false;
}
}
4、注解拦截器
/**
* 注册入参和出参拦截器
*/
@Configuration
@MapperScan("com.jtkj.mapper" )
public class MyBatisPlusConfig {
@Autowired
private ApplicationContext applicationContext;
/**
* 注册MyBatis拦截器
* @param sqlSessionFactory
* @return
*/
@Bean
public String myInterceptor(SqlSessionFactory sqlSessionFactory){
sqlSessionFactory.getConfiguration().addInterceptor(parameterIntercept());
sqlSessionFactory.getConfiguration().addInterceptor(resultSetIntercept());
return "myInterceptor";
}
public MybatisParameterIntercept parameterIntercept(){
return applicationContext.getAutowireCapableBeanFactory().createBean(MybatisParameterIntercept.class);
}
public MybatisResultSetIntercept resultSetIntercept(){
return applicationContext.getAutowireCapableBeanFactory().createBean(MybatisResultSetIntercept.class);
}
}
5、加解密工具类
/**
* 数据库字段加解密工具类
*/
public class EncryptDecryptUtils {
/**
* 多field加密方法
*
* @param declaredFields
* @param parameterObject
* @param <T>
* @return
* @throws IllegalAccessException
*/
public static <T> T encrypt(Field[] declaredFields, T parameterObject,String key) throws IllegalAccessException {
for (Field field : declaredFields) {
EncryptDecryptField annotation = field.getAnnotation(EncryptDecryptField.class);
if (Objects.isNull(annotation)) {
continue;
}
encrypt(field, parameterObject,key);
}
return parameterObject;
}
/**
* 单个field加密方法
*
* @param field
* @param parameterObject
* @param <T>
* @return
* @throws IllegalAccessException
*/
public static <T> T encrypt(Field field, T parameterObject,String key) 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, new SM4Utils(key).encryptData_ECB(value));
}
return parameterObject;
}
/**
* 解密方法
*
* @param result
* @param <T>
* @return
* @throws IllegalAccessException
*/
public static <T> T decrypt(T result,String key) throws IllegalAccessException {
Class<?> parameterObjectClass = result.getClass();
Field[] declaredFields = parameterObjectClass.getDeclaredFields();
decrypt(declaredFields, result,key);
return result;
}
/**
* 多个field解密方法
*
* @param declaredFields
* @param result
* @throws IllegalAccessException
*/
public static void decrypt(Field[] declaredFields, Object result,String key) throws IllegalAccessException {
for (Field field : declaredFields) {
EncryptDecryptField annotation = field.getAnnotation(EncryptDecryptField.class);
if (Objects.isNull(annotation)) {
continue;
}
decrypt(field, result,key);
}
}
/**
* 单个field解密方法
*
* @param field
* @param result
* @throws IllegalAccessException
*/
public static void decrypt(Field field, Object result,String key) 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 decData = new SM4Utils(key).decryptData_ECB(value);
if (StringUtil.isNotEmpty(decData)){
field.set(result, decData);
}else {
field.set(result,value );
}
}
}
}
三、实例
1、controller
@RestController
@Api(tags = "test", value = "tets")
@RequestMapping("/test/")
public class TestController {
@Autowired
private TestService testService;
/**
* 新增
*/
@RequestMapping("add")
public ActionResult add(@RequestBody TbUser user){
testService.add(user);
return ActionResult.success();
}
@GetMapping("list")
public ActionResult list(){
return ActionResult.success(testService.getList());
}
/**
* 新增
*/
@RequestMapping("add1")
public ActionResult add1(@RequestBody TbUser user){
testService.create(user);
return ActionResult.success();
}
@GetMapping("list1")
public ActionResult list1(){
return ActionResult.success(testService.getList1());
}
}
2、service
public interface TestService {
void add(TbUser user);
List<TbUser> getList();
void create(TbUser user);
List<TbUser> getList1();
}
@Service
public class TestServiceImpl extends ServiceImpl<TestMapper, TbUser> implements TestService {
@Autowired
TestMapper testMapper;
@Override
public void add(TbUser user) {
String username = user.getUsername();
String role = user.getRole();
user.setId(RandomUtil.uuId());
testMapper.add(user);
}
@Override
public List<TbUser> getList() {
return testMapper.getList();
}
@Override
public void create(TbUser user) {
user.setId(RandomUtil.uuId());
this.save(user);
}
@Override
public List<TbUser> getList1() {
return this.list();
}
}
3、mapper
public interface TestMapper extends BaseMapper<TbUser> {
}
四、效果展示
五、总结
使用mybatis拦截器方式复用性强,比较灵活,还有一种方式是在mybatis的xml文件中进行加解密处理,需要手写sql,比较麻烦没有贴出来。
更多推荐
所有评论(0)