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. 避免不必要的对象创建:
  • 如果传入的字符串不需要脱敏,或者已经是脱敏后的格式,可以直接返回原字符串,避免创建新的字符串对象。

效果图展示


相关文章
|
4月前
|
Java 数据库连接 数据库
mybatis查询数据,返回的对象少了一个字段
mybatis查询数据,返回的对象少了一个字段
309 8
|
2月前
|
SQL Java 数据库连接
深入 MyBatis-Plus 插件:解锁高级数据库功能
Mybatis-Plus 提供了丰富的插件机制,这些插件可以帮助开发者更方便地扩展 Mybatis 的功能,提升开发效率、优化性能和实现一些常用的功能。
330 26
深入 MyBatis-Plus 插件:解锁高级数据库功能
|
2月前
|
SQL Java 数据库连接
【MyBatisPlus·最新教程】包含多个改造案例,常用注解、条件构造器、代码生成、静态工具、类型处理器、分页插件、自动填充字段
MyBatis-Plus是一个MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。本文讲解了最新版MP的使用教程,包含多个改造案例,常用注解、条件构造器、代码生成、静态工具、类型处理器、分页插件、自动填充字段等核心功能。
【MyBatisPlus·最新教程】包含多个改造案例,常用注解、条件构造器、代码生成、静态工具、类型处理器、分页插件、自动填充字段
|
2月前
|
SQL 缓存 Java
【详细实用のMyBatis教程】获取参数值和结果的各种情况、自定义映射、动态SQL、多级缓存、逆向工程、分页插件
本文详细介绍了MyBatis的各种常见用法MyBatis多级缓存、逆向工程、分页插件 包括获取参数值和结果的各种情况、自定义映射resultMap、动态SQL
【详细实用のMyBatis教程】获取参数值和结果的各种情况、自定义映射、动态SQL、多级缓存、逆向工程、分页插件
|
2月前
|
SQL 缓存 Java
MyBatis如何关闭一级缓存(分注解和xml两种方式)
MyBatis如何关闭一级缓存(分注解和xml两种方式)
90 5
|
2月前
|
Java 数据库连接 mybatis
Mybatis使用注解方式实现批量更新、批量新增
Mybatis使用注解方式实现批量更新、批量新增
61 3
|
3月前
|
SQL JSON Java
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
这篇文章介绍了如何在Spring Boot项目中整合MyBatis和PageHelper进行分页操作,并且集成Swagger2来生成API文档,同时定义了统一的数据返回格式和请求模块。
103 1
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
|
2月前
|
SQL 存储 数据库
深入理解@TableField注解的使用-MybatisPlus教程
`@TableField`注解在MyBatis-Plus中是一个非常灵活和强大的工具,能够帮助开发者精细控制实体类与数据库表字段之间的映射关系。通过合理使用 `@TableField`注解,可以实现字段名称映射、自动填充、条件查询以及自定义类型处理等高级功能。这些功能在实际开发中,可以显著提高代码的可读性和维护性。如果需要进一步优化和管理你的MyBatis-Plus应用程
209 3
|
2月前
|
Java 数据库连接 mybatis
Mybatis使用注解方式实现批量更新、批量新增
Mybatis使用注解方式实现批量更新、批量新增
174 1
|
4月前
|
SQL XML Java
mybatis复习02,简单的增删改查,@Param注解多个参数,resultType与resultMap的区别,#{}预编译参数
文章介绍了MyBatis的简单增删改查操作,包括创建数据表、实体类、配置文件、Mapper接口及其XML文件,并解释了`#{}`预编译参数和`@Param`注解的使用。同时,还涵盖了resultType与resultMap的区别,并提供了完整的代码实例和测试用例。
mybatis复习02,简单的增删改查,@Param注解多个参数,resultType与resultMap的区别,#{}预编译参数
下一篇
开通oss服务