项目中有一个需求,需要将列表中敏感数据做屏蔽,屏蔽数据根据字段数据的不同,也有对应的屏蔽规则。例如:身份证号、手机号、电话、地址等。大致需求就是这样,即根据脱敏规则,对敏感数据做脱敏屏蔽处理。
项目中有很多列表页面,对应的数据库表字段名称也都不一样,如果使用if else 方式去实现,对每个列表接口数据做判断,性能暂且不说,势必会造成代码冗余,使代码阅读性降低,更改起来非常麻烦。
这里想一个思路,
1、先在写一个屏蔽规则方法类(这里命名为MaskingFunction)类中使用java静态方法,实现每种数据的屏蔽规则。
2、然后再写一个配置类(MaskingConfig),将实体类对应的有屏蔽要求的字段的屏蔽规则,配置保存到Map中。
3、最后写一个工具类,这个类传入两个参数,一个是实体类名称(tabName)、一个是要屏蔽的数据(List<Map>)。根据实体类名称可以从配置类中查询到字段的屏蔽规则,然后再循环操作对数据做处理。
代码如下:
MaskingFunction类。实现每种屏蔽规则
package com.ruoyi.masking;
import org.apache.commons.lang3.StringUtils;
/**
* 敏感数据屏蔽规则的实现方法类
*/
public class MaskingFunction {
public static final String replace = "******";
public static final String replace2 = "*";
/**
* 身份证号屏蔽规则实现方法
* 保存前六位,和末尾两位,中间用星号替换
* @param idCard 身份证号
* @return 加工后返回的数据
*/
public static String idCardF(String idCard){
if(StringUtils.isBlank(idCard)){
return idCard;
}
int length = idCard.length();
if(length <= 8){
return idCard;
}
return idCard.substring(0,6) + replace + idCard.substring(length-2);
}
/**
* 手机号屏蔽规则实现方法
* 保存前三位和后四位,中间四位用星号替换
* @param phone
* @return
*/
public static String phoneF(String phone){
if(StringUtils.isBlank(phone)){
return phone;
}
int length = phone.length();
if(length <= 7){
return phone;
}
return phone.substring(0,3) + "****" + phone.substring(length-4);
}
}
MaskingConfig类。配置每个实体类字段的屏蔽规则实现方法
package com.ruoyi.masking;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
/**
* 敏感数据脱敏屏蔽规则配置类
*/
public class MaskingConfig {
private final static Logger log = LoggerFactory.getLogger(MaskingConfig.class);
//保存敏感数据的配置。key是要屏蔽数据的实体类名称,value的map保存每个字段对应的屏蔽方法
public static Map<String,Map<String, Function<String,String>>> mc = new HashMap<>();
//使用静态代码块,将敏感数据屏蔽规则加载到mc中
static {
log.info("----屏蔽规则配置初始化");
//TestTab实体类屏蔽规则配置
mc.put("TestTab",new HashMap<String, Function<String,String>>(){{
//身份证号字段
put("idCard",MaskingFunction::idCardF);
//电话号字段
put("phone",MaskingFunction::phoneF);
}});
//TestTable实体类屏蔽规则配置
mc.put("TestTable",new HashMap<String, Function<String,String>>(){{
//地址
// put("address",MaskingFunction::address);
//电话号字段
put("phone",MaskingFunction::phoneF);
}});
}
}
MaskingUtil类。提供一个静态方法,根据配置完成对数据中敏感数据的屏蔽
package com.ruoyi.masking;
import com.ruoyi.common.utils.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
/**
* 敏感数据屏蔽工具类
*
*/
public class MaskingUtil {
private static Logger log = LoggerFactory.getLogger(MaskingUtil.class);
/**
* 敏感数据屏蔽方法。
* @param entityName 要屏蔽数据的实体类名称
* @param list 数据集合
* @return 屏蔽完成的数据
*/
public static List<Map<String,Object>> masking(String entityName,List<Map<String,Object>> list){
if (StringUtils.isBlank(entityName)){
log.error("-----请传入entityName参数");
return list;
}
if(list==null || list.isEmpty()){
return list;
}
//获取实体类entityName,配置的屏蔽方法
Map<String, Function<String,String>> maskingConfig = MaskingConfig.mc.get(entityName);
if(maskingConfig == null || maskingConfig.size() == 0){
return list;
}
list.forEach(data -> mas(maskingConfig,data));
return list;
}
private static void mas(Map<String, Function<String,String>> maskingConfig, Map<String,Object> data){
if(data == null || data.size() == 0){
return;
}
//获取配置类中字段名称
Set<String> keySet = maskingConfig.keySet();
for (String key : keySet) {
Object value = data.get(key);
if(value == null){
continue;
}
String str = value.toString();
if(StringUtils.isBlank(str)){
continue;
}
Function<String,String> maskingFunction = maskingConfig.get(key);
if (maskingFunction == null){
log.error("-----字段key={},配置的屏蔽方法为空",key);
continue;
}
//执行屏蔽方法,替换data中的数据
data.put(key,maskingFunction.apply(str));
}
}
}
测试类MaskingTest
package com.ruoyi.masking;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import org.apache.poi.ss.formula.functions.T;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* 测试屏蔽方法
*/
public class MaskingTest {
public static void main(String[] args) {
MaskingTest mt = new MaskingTest();
List<TestTab> testTabs = mt.queryTestTab(null);
new JSONObject();
String str = JSON.toJSONString(testTabs);
List<Map<String, Object>> maps = JSON.parseArray(str, (Type) Map.class);
// JSON.parse("sddddd")
// List<Map<String,Object>> list = testTabs.stream().map(t -> n)
System.out.println("屏蔽前");
System.out.println(maps);
MaskingUtil.masking("TestTab",maps);
System.out.println("屏蔽后");
System.out.println(maps);
}
/**
* 这里写一个模拟方法,返回数据。模拟从数据库读取数据
* @param testTab
* @return
*/
public List<TestTab> queryTestTab(TestTab testTab){
List<TestTab> list = new ArrayList<>();
list.add(new TestTab("1","1305291195010101234","15012345678","张三","张三简介"));
list.add(new TestTab("2","1305291195010105566","15012345","李四","李四简介"));
list.add(new TestTab("3","1305291195010101111","1502345","赵六","赵六简介"));
return list;
}
class TestTab {
private String id;
private String idCard;
private String phone;
private String name;
private String desc;
public TestTab() {
}
public TestTab(String id, String idCard, String phone, String name, String desc) {
this.id = id;
this.idCard = idCard;
this.phone = phone;
this.name = name;
this.desc = desc;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getIdCard() {
return idCard;
}
public void setIdCard(String idCard) {
this.idCard = idCard;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
}
}
打印的日志
屏蔽前
[{phone=15012345678, idCard=1305291195010101234, name=张三, id=1, desc=张三简介}, {phone=15012345, idCard=1305291195010105566, name=李四, id=2, desc=李四简介}, {phone=1502345, idCard=1305291195010101111, name=赵六, id=3, desc=赵六简介}]
23:16:57.468 [main] INFO c.r.m.MaskingConfig - [<clinit>,22] - ----屏蔽规则配置初始化
屏蔽后
[{phone=150****5678, idCard=130529******34, name=张三, id=1, desc=张三简介}, {phone=150****2345, idCard=130529******66, name=李四, id=2, desc=李四简介}, {phone=1502345, idCard=130529******11, name=赵六, id=3, desc=赵六简介}]
从日志中看,TestTab这个实体类数据都按照我们配置的方法,执行了敏感数据屏蔽。
实际使用中,如果有其他的敏感数据屏蔽规则,可按照以下操作:
1、以在MaskingFunction这个类中按照规则实现的屏蔽方法。
2、在MackingConfig类中,配置好要屏蔽的实体类名称以及他的字段对应的屏蔽方法(可以参考TestTab的配置)。
3、查询出数据以后,调用MaskingUtil中的masking方法(参考测试类中的调用方式)。
这样就完成了敏感数据屏蔽。
现行的应该有成熟的框架,类似做数据校验这样,在实体类中加注解即可。由于时间等原因未去做相关的调查研究,如果有同行了解这些方面技术还望不吝指教。