插件实战:手写MyBatis数据脱敏插件
【摘要】 数据脱敏是保护敏感信息安全的重要手段,而MyBatis数据脱敏插件可以帮助开发者在数据库层面对敏感数据进行脱敏处理。本文将介绍如何手动实现一个自定义的MyBatis数据脱敏插件,详细解析其原理和实现方式。我们将探讨数据脱敏的基本原理、脱敏策略,以及如何结合MyBatis的拦截器机制来实现自定义的数据脱敏功能。
前言
在数字时代,数据安全问题备受关注。想象一下,你的应用程序可能在处理各种敏感信息,例如用户的身份证号码、银行卡号等。如果这些信息泄露,后果不堪设想!但别担心,今天我们将揭开 MyBatis 数据脱敏的神秘面纱,让你的数据像戴着隐形护甲一样安全。
数据脱敏的实现方式
我认为数据脱敏主要可分为两种情况。首先,是在数据入库时进行脱敏处理,这意味着在存储之前就对敏感数据进行加密,比如可以使用类似于密码盐加密的方式进行加密。第二种情况是在从数据库查询出数据后进行脱敏处理。在这种情况下,脱敏的位置可以灵活选择,可以在查询结果立即脱敏,也可以在控制器层面进行脱敏处理。举例来说,可以利用 AOP 在方法执行后执行脱敏逻辑。这里我主要讲的是第二种中的查询结果立即脱敏。
构思
从哪个地方进行脱敏?
首先对于 MyBatis 来说,它其实也是遵守像传统的 JDBC 操作的,只不过它在这其中点了几朵花。具体来说也就是下面的几步:
- Class.forName 注册驱动
- 获取一个 Connection 对象
- 创建一个 Statement 对象
- execute() 方法执行 SQL 语句,获取 ResultSet 结果集
- 通过 ResultSet 结果集给 POJO 的属性赋值
- 最后关闭相关的资源
通过上面的,我们就能知道我们需要拦截的位置了,也就是在 ResultSet 结果集那里,在 MyBatis 中也就是
org.apache.ibatis.executor.resultset.ResultSetHandler#handleResultSets
方法
具体来说,ResultSetHandler
是 MyBatis 中的一个接口,它定义了数据库查询结果集处理的方法。其中,handleResultSets
方法用于处理从数据库返回的结果集。在 MyBatis 中,查询结果可以是单个对象、对象列表或映射,而 handleResultSets
方法则负责将这些查询结果转换为 Java 对象或集合。
它怎么知道我什么数据需要脱敏
在刚刚思路的基础上我们需要明白,如何找到你标记为脱敏的数据,以及你如何标记脱敏。多想一步的话,我们就应该知道,我们脱敏的可能目前仅仅有手机号,身份证号,但是保不准以后就会有别的了,而且单纯的在拦截器中根据字段名称编码也不现实,于是就有了注解,比如对于user
表中的phone
我们需要脱敏,那么只需要在实体类的这个字段下加个注解即可。
项目实现
这里我就不再过多的赘述了,直接贴代码
拦截器实现
package world.xuewei.interceptor;
import world.xuewei.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 薛伟
*/
@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 world.xuewei.annotation;
import world.xuewei.Enum.DesensitizeType;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author 薛伟
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Desensitize {
// 定义脱敏策略,可以扩展更多类型
DesensitizeType type() default DesensitizeType.PHONE;
}
枚举实现
package world.xuewei.Enum;
import java.util.function.Function;
/**
* @author 薛伟
*/
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);
}
// 可以添加更多的脱敏类型
}
如果你追求特别完美,或者极致,你也可以优化上面的代码,具体从以下几点优化:
- 预编译正则表达式:
- 每次调用
desensitize
方法时,都会创建一个新的正则表达式模式。 - 预编译正则表达式,并将它们作为
Pattern
对象存储,可以减少正则表达式编译的开销。
- 每次调用
- 减少 Lambda 表达式创建的开销:
- 每个枚举实例都会创建一个 Lambda 表达式。
- 可以考虑将脱敏逻辑移到一个静态方法中,并在枚举构造器里引用这个方法,减少 Lambda 表达式的创建。
- 避免不必要的对象创建:
- 如果传入的字符串不需要脱敏,或者已经是脱敏后的格式,可以直接返回原字符串,避免创建新的字符串对象。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
作者其他文章
评论(0)