1使用场景
你平时肯定做过这样的需求。要求展示用户的手机号,但是不能完全展示,需要在中间给手机号打码,如下图:
我们将关键数据做了适当隐藏,这样就叫数据脱敏。
2数据脱敏
数据脱敏又称数据去隐私化或数据变形,是在给定的规则、策略下对敏感数据进行变换、修改的技术机制,能够在很大程度上解决敏感数据在非可信环境中使用的问题。根据数据保护规范和脱敏策略.对业务数据中的敏感信息实施自动变形.实现对敏感信息的隐藏。
我们来看看具体需求,现在要求对以下数据的电话号码和身份证号码适当隐藏处理:
要求脱敏处理后的效果为:
如上图,我们将手机号与身份证号做了适当的隐藏。
我们常规的做法是先获取到userInfo
,然后获取mobile
和idCard
,再将mobile
和idCard
做相应的脱敏处理,最后再set
进userInfo
中。
但这里有个问题这里我们调用了getUserInfo()
后,采用了大量的代码去专门处理脱敏数据,而实际上我们只是要获取userInfo
的信息而已,为此我决定采用注解的形式,将数据进行数据脱敏即可。
3代码实现
我们先列出数据脱敏的类型
上面为我们需要脱敏的数据枚举。
然后我们根据不同的枚举类型调用各自的脱敏方法
我们还要编写一个处理序列化的类
我们在SensitiveDataSerialize
类中重写了serialize()
,里面调用了我们之前的jsonHandler(s, jsonGenerator)
数据脱敏的方法,这个方法根据不同的枚举类型实现对应的数据脱敏。
然后我们还要重写createContextual()
方法,这里面实现的功能就是扫描脱敏注解然后实现各自的数据脱敏。
接下来我们定义脱敏注解。
这个value()
是我们传入的类型枚举。
我们来看看具体的脱敏方法,脱敏方法我主要写在这个SensitiveInfoUtils
工具类中。这里我们来看一个手机号脱敏方法。
简单来说,就是字符串的截取和替换。
我们在实体类中标上数据脱敏的注解
根据业务需求,我们对手机号和身份证进行数据脱敏。
最后我们在Controller层写一个接口:
启动工程运行接口,获取如开头的结果:
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;
}
如果再增加其他类型,那么这里就要增加其他类型的代码,这个方法就需要修改,这样就破坏了代码的封装性。因此我决定将其改造成策略模式。
我们来看看目录,这里每个类型对应一个策略类,首先我们来看策略接口:
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数据如下:
之后我们在serialize()
方法中调用:
这我们通过SpringContextHolder.getBean()
获取容器中的sensitiveStrategyService
实例,然后就是调用方法。
我们同样获取了想要的结果。
使用策略模式,我们需要增加类型时,只需要新增一个策略类,在里面重写好对应的方法,其他地方都不需要修改。