1使用场景

你平时肯定做过这样的需求。要求展示用户的手机号,但是不能完全展示,需要在中间给手机号打码,如下图:

java 脱敏敏感信息方案_设计模式

我们将关键数据做了适当隐藏,这样就叫数据脱敏。

2数据脱敏

数据脱敏又称数据去隐私化数据变形,是在给定的规则、策略下对敏感数据进行变换、修改的技术机制,能够在很大程度上解决敏感数据在非可信环境中使用的问题。根据数据保护规范和脱敏策略.对业务数据中的敏感信息实施自动变形.实现对敏感信息的隐藏。

我们来看看具体需求,现在要求对以下数据的电话号码和身份证号码适当隐藏处理:

java 脱敏敏感信息方案_java_02

要求脱敏处理后的效果为:

java 脱敏敏感信息方案_java_03

如上图,我们将手机号与身份证号做了适当的隐藏。

java 脱敏敏感信息方案_设计模式_04

我们常规的做法是先获取到userInfo,然后获取mobileidCard,再将mobileidCard做相应的脱敏处理,最后再setuserInfo中。

但这里有个问题这里我们调用了getUserInfo()后,采用了大量的代码去专门处理脱敏数据,而实际上我们只是要获取userInfo的信息而已,为此我决定采用注解的形式,将数据进行数据脱敏即可。

3代码实现

我们先列出数据脱敏的类型

java 脱敏敏感信息方案_数据_05

上面为我们需要脱敏的数据枚举。

然后我们根据不同的枚举类型调用各自的脱敏方法

java 脱敏敏感信息方案_java_02

我们还要编写一个处理序列化的类

java 脱敏敏感信息方案_数据_07

我们在SensitiveDataSerialize类中重写了serialize(),里面调用了我们之前的jsonHandler(s, jsonGenerator)数据脱敏的方法,这个方法根据不同的枚举类型实现对应的数据脱敏。

然后我们还要重写createContextual()方法,这里面实现的功能就是扫描脱敏注解然后实现各自的数据脱敏。

接下来我们定义脱敏注解。

java 脱敏敏感信息方案_编程语言_08

这个value()是我们传入的类型枚举。

我们来看看具体的脱敏方法,脱敏方法我主要写在这个SensitiveInfoUtils工具类中。这里我们来看一个手机号脱敏方法。

java 脱敏敏感信息方案_java_02

简单来说,就是字符串的截取和替换。

我们在实体类中标上数据脱敏的注解

java 脱敏敏感信息方案_java 脱敏敏感信息方案_10

根据业务需求,我们对手机号和身份证进行数据脱敏。

最后我们在Controller层写一个接口:

java 脱敏敏感信息方案_数据_11

启动工程运行接口,获取如开头的结果:

java 脱敏敏感信息方案_java_03

4关于代码封装性思考

前面我们写的jsonHandler()方法是根据类型枚举调用对应的脱敏方法

switch (this.type) {
    case CHINESE_NAME: {
        jsonGenerator.writeString(SensitiveInfoUtils.chineseName(s));
        break;
    }
    case ID_CARD: {
        jsonGenerator.writeString(SensitiveInfoUtils.idCardNum(s));
        break;
    }
    case FIXED_PHONE: {
        jsonGenerator.writeString(SensitiveInfoUtils.fixedPhone(s));
        break;
    }
    case MOBILE_PHONE: {
        jsonGenerator.writeString(SensitiveInfoUtils.mobilePhone(s));
        break;
    }
    case ADDRESS: {
        jsonGenerator.writeString(SensitiveInfoUtils.address(s, 4));
        break;
    }
    case EMAIL: {
        jsonGenerator.writeString(SensitiveInfoUtils.email(s));
        break;
    }
    case BANK_CARD: {
        jsonGenerator.writeString(SensitiveInfoUtils.bankCard(s));
        break;
    }
}

但是我们如果要增加脱敏类型,比如增加学校SCHOOL,那么这个方法就要增加

...
    case SCHOOL: {
        jsonGenerator.writeString(SensitiveInfoUtils.school(s));
        break;
    }

如果再增加其他类型,那么这里就要增加其他类型的代码,这个方法就需要修改,这样就破坏了代码的封装性。因此我决定将其改造成策略模式。

java 脱敏敏感信息方案_java_13

我们来看看目录,这里每个类型对应一个策略类,首先我们来看策略接口:

public interface SensitiveStrategy {

    SensitiveType getSensitiveType();

    String maskingData(String str);
}

我们定义了两个方法,getSensitiveType()是获取对应枚举类型的方法,maskingData(String str)是实现数据脱敏的方法。因为每个实现的策略类大同小异,这里我们看一个策略类,以MobilePhoneStrategy为例。我们来看看代码:

@Component
public class MobilePhoneStrategy implements SensitiveStrategy {

    @Override
    public SensitiveType getSensitiveType() {
        return SensitiveType.MOBILE_PHONE;
    }

    @Override
    public String maskingData(String str) {
        return SensitiveInfoUtils.mobilePhone(str);
    }
}

代码中我们返回了对应的手机类型枚举,和调用了对应的手机号脱敏方法。

然后我们写一个service类:

@Service
public class SensitiveStrategyService {
    Map<SensitiveType, SensitiveStrategy> map = Maps.newHashMap();

    public SensitiveStrategyService(List<SensitiveStrategy> sensitiveStrategyList) {
        sensitiveStrategyList.forEach(sensitiveStrategy -> map.put(sensitiveStrategy.getSensitiveType(), sensitiveStrategy));
    }

    public String generatorSensitive(SensitiveType typeEnum, String str) {
        SensitiveStrategy sensitiveStrategy = map.get(typeEnum);
        if (sensitiveStrategy != null) {
            return sensitiveStrategy.maskingData(str);
        }
        return StringUtils.EMPTY;
    }

}

这里我们用map存入枚举类型和对应的策略类,map数据如下:

java 脱敏敏感信息方案_编程语言_14

之后我们在serialize()方法中调用:

java 脱敏敏感信息方案_编程语言_15

这我们通过SpringContextHolder.getBean()获取容器中的sensitiveStrategyService实例,然后就是调用方法。

我们同样获取了想要的结果。

使用策略模式,我们需要增加类型时,只需要新增一个策略类,在里面重写好对应的方法,其他地方都不需要修改。