# 脱敏插件

有时候业务需求,需要对手机号,姓名,身份证等脱敏处理,比如电话 134****6657...

脱敏操作实现方式很多种,这里我们通过mybatis插件实现,当数据查出来时,对ResultSet结果集处理。

# 思路

  1. 拦截四大对象之一的 ResultSetHandler 结果集对象的 handleResultSets 结果集处理方法。重写 intercept 方法,在业务逻辑执行 invocation.proceed(); 后做后置逻辑处理,对结果集的每一条记录做处理 records.forEach(this::desensitize);
@Component
@Intercepts(@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = Statement.class))
public class DesensitizePlugin implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        List<Object> records = (List<Object>) invocation.proceed();
        records.forEach(this::desensitize);
        return records;
    }

    private void desensitize(Object source) {
        Class<?> sourceClass = source.getClass();
        // mybatis提供的工具类,获取基础数据
        MetaObject metaObject = SystemMetaObject.forObject(source);
        Stream.of(sourceClass.getDeclaredFields())
                .filter(field -> field.isAnnotationPresent(Desensitize.class))
                .forEach(field -> doDesensitize(metaObject, field));
    }

    private void doDesensitize(MetaObject metaObject, Field field) {
        //属性名称
        String name = field.getName();
        //获取属性值
        Object value = metaObject.getValue(name);
        //值不为null,并且是String类型
        if (value != null && metaObject.getGetterType(name) == String.class) {
            Desensitize annotation = field.getAnnotation(Desensitize.class);
            //获取脱敏策略 进行脱敏
            DesensitizeStrategy strategy = annotation.strategy();
            Object o = strategy.getDesensitizer().apply((String) value);
            //脱敏后的值写回去
            metaObject.setValue(name, o);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
  1. 脱敏处理,就需要根据每一条记录的对象的属性去判断,我们采用注解方式,对需要处理的属性加注解,注解需要指定脱敏策略,所以属性通过一个枚举对象去定义脱敏策略类型 DesensitizeStrategy,这里,枚举里用到了java8的新特性 Function 接口去实现策略的。

Function<String, String> 接口相当于一个工具类一样,可以通过执行 apply 方法去调用lambda表达式去做些逻辑替换,例如 USERNAME(s -> s.replaceAll("(\\S)\\S(\\S*)", "$1*$2"))

// User.java 实体类

public class User {

    private Integer id;

    //脱敏注解 根据USERNAME这个策略
    @Desensitize(strategy = DesensitizeStrategy.USERNAME)
    private String name;

    //省略其他字段和get/set 
}
1
2
3
4
5
6
7
8
9
10

// 注解 Desensitize.java

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Desensitize {

    DesensitizeStrategy strategy();

}
1
2
3
4
5
6
7

// DesensitizeStrategy.java 策略枚举,我们可以自定义策略,写这里就行

public enum DesensitizeStrategy {

    /**
     * Username sensitive strategy.
     */
    USERNAME(s -> s.replaceAll("(\\S)\\S(\\S*)", "$1*$2")),
    /**
     * Id card sensitive type.
     */
    ID_CARD(s -> s.replaceAll("(\\d{4})\\d{10}(\\w{4})", "$1****$2")),
    /**
     * Phone sensitive type.
     */
    PHONE(s -> s.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2")),

    /**
     * Address sensitive type.
     */
    ADDRESS(s -> s.replaceAll("(\\S{8})\\S{4}(\\S*)\\S{4}", "$1****$2****"));


    private final Desensitizer desensitizer;

    DesensitizeStrategy(Desensitizer desensitizer) {
        this.desensitizer = desensitizer;
    }

    public Desensitizer getDesensitizer() {
        return desensitizer;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

// Desensitizer.java 相当于工具类一样

public interface Desensitizer extends Function<String, String> {
}
1
2
  1. 脱敏的逻辑
  • 先获取到记录的类类型 Class<?> sourceClass = source.getClass(); 其实就是获取到User类了,获取到类之后,就可以取到属性以及属性上加的脱敏注解 @Desensitize 及其策略类型,比如 DesensitizeStrategy.USERNAME

  • 还需要每一条记录record的属性对应的value值,比如 name="程咬金",获取值mybatis提供了一个工具类 MeteObject

private void desensitize(Object source) {
    Class<?> sourceClass = source.getClass();
    // mybatis提供的工具类,获取基础数据
    MetaObject metaObject = SystemMetaObject.forObject(source);
    Stream.of(sourceClass.getDeclaredFields())
            .filter(field -> field.isAnnotationPresent(Desensitize.class))
            .forEach(field -> doDesensitize(metaObject, field));
}
1
2
3
4
5
6
7
8
  • 执行脱敏操作,参数是可以获取到属性值的 MetaObject 和属性 Field
private void doDesensitize(MetaObject metaObject, Field field) {
    //属性名称
    String name = field.getName();
    //获取属性值
    Object value = metaObject.getValue(name);
    //值不为null,并且是String类型
    if (value != null && metaObject.getGetterType(name) == String.class) {
        Desensitize annotation = field.getAnnotation(Desensitize.class);
        //获取脱敏策略 进行脱敏
        DesensitizeStrategy strategy = annotation.strategy();
        Object o = strategy.getDesensitizer().apply((String) value);
        //脱敏后的值写回去
        metaObject.setValue(name, o);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

注意,这里需要把插件 DesensitizePlugin 丢到Spring容器里才能起作用

  • 方式一、 使用注解注入 @component
  • 方式二、通过@Bean注入
@Configuration
public class MybatisPluginsConfig {
    @Bean
    public DesensitizePlugin desensitizePlugin() {
        return new DesensitizePlugin();
    }
}
1
2
3
4
5
6
7