数据安全篇-大范围数据脱敏
一、背景:
公司因为数据安全需要做数据的脱敏,然而涉及的范围是全服务器!个别字段脱敏很快,影响整个服务的脱敏要一个一个的加我宁愿嘎!
二、解决方案:
1、一个一个加到死。
2、AOP一劳永逸。
三、设计思路
由于业务复杂,返回的对象里可能会含有多种脱敏规则的字段,且对象里同一脱敏规则下可能会含有多个需要脱敏的字段,所以设计DesensitizationEnum枚举类来解决此问题(主要是防止需求变更突然多几个字段要脱敏),并返回列表原对象类型(如果不是多层嵌套可用反射解决该问题),方便导出时列表查询接口的复用。
eg:
{
"userName": "测试",
"userName1": "测试1",
"userName2": "测试2",
"userName3": "测试3"
}
四、代码展现
枚举类
@Getter
public enum DesensitizationEnum {
/**
* 手机号脱敏 中间4位
*/
MOBILE_PHONE(s -> s.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1*****$2"), new String[]{"mobileNumber","phoneNumber", "phone", "mobile"}),
/**
* 名称、对方账户 只保留姓
*/
USER_NAME(s -> s.substring(0, 1) + s.substring(1).replaceAll("\\S", "*"), new String[]{"userName", "userName1", "userName2", "userName3"}),
/**
* 身份证 保留前六位和后四位
*/
PERSON_ID_CARD(s -> s.replaceAll("(\\d{6})\\d*(\\d{4})", "$1********$2"), new String[]{"personIdCard"}),
/**
* 邮箱 @之前都脱敏
*/
EMAIL(s -> s.replaceAll("(.*)@", "******@"), new String[]{"email", "mchtEmail"}),
/**
* 商户ID 商户key 包留前4位
*/
MCH(s -> s.replaceAll("(.{4}).*", "$1*****"), new String[]{"mchId"}),
/**
* 地址脱敏 省略后5位
*/
ADDRESS(s -> s.replaceAll("(.{5})$", "*****"), new String[]{"address", "recipientAddress"}),
/**
* 全都脱敏
*/
PASSWORD(s -> s.replaceAll("\\S", "*"), new String[]{"password"}),
/**
* 保留前6位
*/
ROOT_ACC_NO(s -> s.replaceAll("(\\d{6})\\d*", "$1************"), new String[]{"rootAccNo"})
;
/**
* 成员变量 是一个接口类型
*/
private Function<String, String> function;
private String[] field;
DesensitizationEnum(Function<String, String> function, String[] field) {
this.function = function;
this.field = field;
}
public Function<String, String> function() {
return this.function;
}
public static String getApply(DesensitizationEnum desensitizationEnum, Object str) {
if (!ObjectUtils.allNotNull(str) || StringUtils.isBlank(str.toString())) {
return "";
}
return desensitizationEnum.getFunction().apply(str.toString());
}
}
注解类
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DesensitizationAnnotation {
DesensitizationEnum[] desensitizationEnum();
Class resultClz();
}
切面类
@Slf4j
@Aspect
@Component
public class DesensitizationAnnotationAspect {
@Around("@annotation(desensitizationAnnotation)")
public Object around(ProceedingJoinPoint jp, DesensitizationAnnotation desensitizationAnnotation) throws Throwable {
Object proceed = jp.proceed();
try {
if (!ObjectUtils.allNotNull(proceed)) {
return proceed;
}
log.info("脱敏原数据为:[{}]", proceed);
JSONObject jsonObject = JSONObject.parseObject(JSON.toJSONStringWithDateFormat(proceed, "yyyy-MM-dd HH:mm:ss", SerializerFeature.WriteDateUseDateFormat));
List resultListJson = JSONObject.parseObject(jsonObject.get("resultList").toString(), List.class);
List resultList = Lists.newArrayList();
if (CollectionUtils.isEmpty(resultListJson)) {
return proceed;
}
resultListJson.stream().forEach(list -> {
JSONObject listJsonObject = JSONObject.parseObject(list.toString());
DesensitizationEnum[] desensitizationEnums = desensitizationAnnotation.desensitizationEnum();
Arrays.stream(desensitizationEnums).sorted().forEach(desensitizationEnum -> {
Arrays.stream(desensitizationEnum.getField()).sorted().forEach(field -> {
if (ObjectUtils.allNotNull(listJsonObject.get(field)) && StringUtils.isNotBlank(listJsonObject.get(field).toString())) {
String str = listJsonObject.get(field).toString();
String apply = desensitizationEnum.function().apply(str);
listJsonObject.put(field, apply);
}
});
});
resultList.add(listJsonObject);
});
List result = JSONObject.parseArray(JSON.toJSONString(resultList), desensitizationAnnotation.resultClz());
jsonObject.put("resultList", result);
if (ObjectUtils.allNotNull(jsonObject)) {
PageQueryDto pageQueryDto = (PageQueryDto)proceed;
pageQueryDto.setResultList(result);
proceed = pageQueryDto;
}
} catch (Exception e) {
log.error("DesensitizationAnnotationAspect error:", e);
return proceed;
}
log.info("脱敏后数据为:[{}]", proceed);
return proceed;
}
}
五、Demo展现
Demo
@DesensitizationAnnotation(desensitizationEnum = {DesensitizationEnum.USER_NAME, DesensitizationEnum.MOBILE_PHONE, DesensitizationEnum.ROOT_ACC_NO, DesensitizationEnum.MCH, DesensitizationEnum.PERSON_ID_CARD}, resultClz = testDto.class)
@Override
public void testDesensitization() {
return ;
}
———— What is worth doing is worth doing well.