mybatis自制插件+注解实现数据脱敏

简介: mybatis自制插件+注解实现数据脱敏

前言

在数字时代,数据安全问题备受关注。想象一下,你的应用程序可能在处理各种敏感信息,例如用户的身份证号码、银行卡号等。如果这些信息泄露,后果不堪设想!但别担心,今天我们将揭开 MyBatis 数据脱敏的神秘面纱,让你的数据像戴着隐形护甲一样安全。

数据脱敏的实现方式

我认为数据脱敏主要可分为两种情况。首先,是在数据入库时进行脱敏处理,这意味着在存储之前就对敏感数据进行加密,比如可以使用类似于密码盐加密的方式进行加密。第二种情况是在从数据库查询出数据后进行脱敏处理。在这种情况下,脱敏的位置可以灵活选择,可以在查询结果立即脱敏,也可以在控制器层面进行脱敏处理。举例来说,可以利用AOP在方法执行后执行脱敏逻辑。这里我主要讲的是第二种中的查询结果立即脱敏。

构思

从哪个地方进行脱敏?

首先对于mybatis来说,它其实也是遵守像传统的JDBC操作的,只不过它在这其中点了几朵花。具体来说也就是下面的几步:

  1. Class.forName注册驱动
  2. 获取一个Connection对象
  3. 创建一个Statement对象
  4. execute()方法执行SQL语句,获取ResultSet结果集
  5. 通过ResultSet结果集给POJO的属性赋值
  6. 最后关闭相关的资源

通过上面的,我们就能知道我们需要拦截的位置了,也就是在ResultSet结果集那里,在mybatis中也就是org.apache.ibatis.executor.resultset.ResultSetHandler#handleResultSets方法

具体来说,ResultSetHandler 是 MyBatis 中的一个接口,它定义了数据库查询结果集处理的方法。其中,handleResultSets 方法用于处理从数据库返回的结果集。在 MyBatis 中,查询结果可以是单个对象、对象列表或映射,而 handleResultSets 方法则负责将这些查询结果转换为 Java 对象或集合。

它怎么知道我什么数据需要脱敏

在1的基础上我们需要明白,如何找到你标记为脱敏的数据,以及你如何标记脱敏。多想一步的话,我们就应该知道,我们脱敏的可能目前仅仅有手机号,身份证号,但是保不准以后就会有别的了,而且单纯的在拦截器中根据字段名称编码也不现实,于是就有了注解,比如对于user表中的phone我们需要脱敏,那么只需要在实体类的这个字段下加个注解即可

项目实现

这里我就不再过多的赘述了,直接贴代码

拦截器实现

package com.todoitbo.baseSpringbootDasmart.interceptor;
import com.todoitbo.baseSpringbootDasmart.annotation.Desensitize;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.plugin.*;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
import java.sql.Statement;
import java.util.List;
import java.util.Properties;
/**
 * @author xiaobo
 */
@Intercepts({@Signature(
        type = ResultSetHandler.class,
        method = "handleResultSets",
        args = {Statement.class}
)})
@Component
public class DesensitizeInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 执行结果处理前的逻辑
        Object result = invocation.proceed();
        // 对结果进行脱敏处理
        if (result instanceof List) {
            List<?> list = (List<?>) result;
            for (Object obj : list) {
                desensitize(obj);
            }
        } else {
            desensitize(result);
        }
        return result;
    }
    private void desensitize(Object obj) {
        if (obj == null) {
            return;
        }
        Field[] fields = obj.getClass().getDeclaredFields();
        for (Field field : fields) {
            // 检查字段上是否存在Desensitize注解
            if (field.isAnnotationPresent(Desensitize.class)) {
                Desensitize desensitize = field.getAnnotation(Desensitize.class);
                try {
                    // 私有字段可以访问
                    field.setAccessible(true);
                    Object value = field.get(obj);
                    if (value instanceof String) {
                        // 字段脱敏
                        String desensitizedValue = desensitize.type().desensitize((String) value);
                        // 设置脱敏后的值
                        field.set(obj, desensitizedValue);
                    }
                } catch (IllegalAccessException e) {
                    // 处理异常
                }
            }
        }
    }
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }
    @Override
    public void setProperties(Properties properties) {
        // 可以通过配置文件传入参数
    }
}

注解实现

package com.todoitbo.baseSpringbootDasmart.annotation;
import com.todoitbo.baseSpringbootDasmart.Enum.DesensitizeType;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * @author xiaobo
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Desensitize {
    // 定义脱敏策略,可以扩展更多类型
    DesensitizeType type() default DesensitizeType.PHONE;
}

枚举实现

package com.todoitbo.baseSpringbootDasmart.Enum;
import java.util.function.Function;
/**
 * @author todoitbo
 * @date 2024/4/12
 */
// 脱敏策略枚举
public enum DesensitizeType {
    PHONE(s -> s.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2")),
    EMAIL(s -> s.replaceAll("(\\w+)\\w{3}@(\\w+)", "$1***@$2"));
    // ...其他脱敏类型
    private final Function<String, String> desensitizer;
    DesensitizeType(Function<String, String> desensitizer) {
        this.desensitizer = desensitizer;
    }
    public String desensitize(String value) {
        return desensitizer.apply(value);
    }
    private String desensitizeValue(String value, DesensitizeType type) {
        return type.desensitize(value);
    }
    // 可以添加更多的脱敏类型
}

如果你追求特别完美,或者极致,你也可以优化上面的代码,具体从以下几点优化:

  1. 预编译正则表达式:
  • 每次调用desensitize方法时,都会创建一个新的正则表达式模式。
  • 预编译正则表达式,并将它们作为Pattern对象存储,可以减少正则表达式编译的开销。
  1. 减少lambda表达式创建的开销:
  • 每个枚举实例都会创建一个lambda表达式。
  • 可以考虑将脱敏逻辑移到一个静态方法中,并在枚举构造器里引用这个方法,减少lambda表达式的创建。
  1. 避免不必要的对象创建:
  • 如果传入的字符串不需要脱敏,或者已经是脱敏后的格式,可以直接返回原字符串,避免创建新的字符串对象。

效果图展示


相关文章
|
11天前
|
Java 数据库连接 Maven
文本,使用SpringBoot工程创建一个Mybatis-plus项目,Mybatis-plus在编写数据层接口,用extends BaseMapper<User>继承实体类
文本,使用SpringBoot工程创建一个Mybatis-plus项目,Mybatis-plus在编写数据层接口,用extends BaseMapper<User>继承实体类
|
20天前
|
Java 数据库连接 数据库
Springboot整合mybatis注解版(202005)
Springboot整合mybatis注解版(202005)
20 3
|
20天前
|
SQL Java 数据库连接
2万字实操案例之在Springboot框架下基于注解用Mybatis开发实现基础操作MySQL之预编译SQL主键返回增删改查
2万字实操案例之在Springboot框架下基于注解用Mybatis开发实现基础操作MySQL之预编译SQL主键返回增删改查
26 2
|
20天前
MyBatis-Plus分页插件基于3.3.2
MyBatis-Plus分页插件基于3.3.2
17 0
MyBatis-Plus分页插件基于3.3.2
|
9天前
|
数据库
MybatisPlus3---常用注解,驼峰转下滑线作为表明 cteateTime 数据表中的 cteate_time,@TableField,与数据库字段冲突要使用转义字符“`order`“,is
MybatisPlus3---常用注解,驼峰转下滑线作为表明 cteateTime 数据表中的 cteate_time,@TableField,与数据库字段冲突要使用转义字符“`order`“,is
|
10天前
|
关系型数据库 MySQL 数据库
MybatisPlus添加数据数据库没有数据,数据消失,使用Navicate看不到数据,Navicate中Mysql的数据与idea的数据不一定同步,Navicate与idea的数据库同步,其实有分页
MybatisPlus添加数据数据库没有数据,数据消失,使用Navicate看不到数据,Navicate中Mysql的数据与idea的数据不一定同步,Navicate与idea的数据库同步,其实有分页
|
16天前
|
SQL 缓存 Java
Java框架之MyBatis 07-动态SQL-缓存机制-逆向工程-分页插件
Java框架之MyBatis 07-动态SQL-缓存机制-逆向工程-分页插件
|
17天前
|
SQL Java 数据库连接
IDEA插件(MyBatis Log Free)
IDEA插件(MyBatis Log Free)
19 0
|
17天前
|
Java 数据库连接 mybatis
idea无法下载Mybatis插件怎么办
idea无法下载Mybatis插件怎么办
|
18天前
|
SQL Java 数据库连接
MyBatis插件深度解析:功能、原理、使用、应用场景与最佳实践
MyBatis插件深度解析:功能、原理、使用、应用场景与最佳实践