前言:
好像没有什么要描述的…
就是关键信息需要脱敏处理
设想是在网关的response拦截里对需要拦截的接口、需要脱敏的属性值进行统一处理
拦截的接口和属性配置到nacos的网关配置中
import java.util.HashMap;
/**
* 关键字脱敏规则
* @date 2020/12/25 10:23
* @author wei.heng
*/
public class KeyWordPatterns extends HashMap<String, String> {
/**
* 增加脱敏规则
* @param key 需要做脱敏处理的字段 - 与入参,对象属性名或map的key 能做equals匹配
* @param pattern 脱敏规则,如:3-6(第四个字符到第六个字符,进行模糊处理,替换为*号)
* @date 2020/12/25 11:28
* @author wei.heng
*/
public void addPattern(String key, String pattern){
super.put(key, pattern);
};
}
/**
*
* 关键信息脱敏规则异常
* @date 2020/12/25 11:41
* @author wei.heng
*/
public class UnsupportedKeyWordsPattern extends RuntimeException {
private static final long serialVersionUID = 1L;
private Integer code = HttpStatus.ERROR;
private String message = "脱敏规则配置有误";
public UnsupportedKeyWordsPattern() {
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
@Override
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
/**
* 关键信息脱敏处理
* 设计思路:nacos的网关里配置需要拦截的接口,需要脱敏的属性值,注入到代码中,再以参数形式使用该脱敏工具类进行统一处理
* 个人感觉这样会比较灵活
* @author wei.heng
* @date 2020/12/25 10:15
*/
public class KeyWordsFilterUtil {
private Logger log = LoggerFactory.getLogger(this.getClass());
/** 脱敏数据类型 */
private final String CAN_BE_MODIFY_TYPE = "String";
/** 用于替换敏感信息的字符 */
private final String REPLACE_CHAR = "*";
public void filter(Object data, KeyWordPatterns patterns) {
if (data instanceof List) {
List<Object> list = (List) data;
for (Object o : list) {
filter(o, patterns);
}
} else if (data instanceof Map) {
// 不推荐接口间交互以map的形式
resetMapValue((Map) data, patterns);
} else {
resetObjectProperties(data, patterns);
}
}
/**
*
* 重设map里需要模糊处理的value值
* @param data map数据对象
* @param patterns 模糊匹配规则
* @date 2020/12/25 14:12
* @author wei.heng
*/
private void resetMapValue(Map data, KeyWordPatterns patterns) {
Set<String> filterKeys = patterns.keySet();
Map<String, Object> map = data;
Set<String> objKeys = map.keySet();
for (String objKey : objKeys) {
if(!filterKeys.contains(objKey)){
continue;
}
Object objValue = map.get(objKey);
if(objValue instanceof String){
String replacedStr = getReplacedString(patterns.get(objKey), objValue.toString());
// 用模糊处理后的数据,覆盖原有值
map.put(objKey, replacedStr);
} else {
// 当做对象进行处理
resetObjectProperties(objValue, patterns);
}
}
}
/**
*
* 重设对象需要模糊处理的属性值
* @param data 数据对象
* @param patterns 模糊处理匹配规则
* @date 2020/12/25 14:12
* @author wei.heng
*/
private void resetObjectProperties(Object data, KeyWordPatterns patterns) {
if(data == null){
return;
}
// 取需要过滤的关键字key
Set<String> keys = patterns.keySet();
Class<?> clazz = data.getClass().getSuperclass();
Field[] fields = clazz.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
field.setAccessible(true);
// 获取对象属性名称
String attributeName = field.getName();
// 获取对象属性类型
String simpleName = field.getType().getSimpleName();
try {
// 是否复杂数据对象
Object valObj = field.get(data);
if(isComplexType(simpleName)) {
// 如果是复杂数据对象,执行递归操作
resetObjectProperties(valObj, patterns);
};
// 不是字符串,不做过滤
if (!CAN_BE_MODIFY_TYPE.equals(simpleName)) {
continue;
}
// 不属于要过滤的字段,不做过滤
if (!keys.contains(attributeName)) {
continue;
}
// 前面已做过类型判断了,走到这里必须是String类型
String valStr = valObj.toString();
String pattern = patterns.get(attributeName);
String replacedStr = getReplacedString(pattern, valStr);
field.set(data, replacedStr);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
/**
*
*
* @param pattern 脱敏规则,如:3-6(第四个字符到第六个字符,进行模糊处理,替换为*号)
* @param valStr 需要做脱敏处理的字符串
* @return java.lang.String
* @date 2020/12/25 13:59
* @author wei.heng
*/
private String getReplacedString(String pattern, String valStr) {
// 是否符合脱敏规则
final String regexPattern = "\\d+-\\d+";
boolean isMatch = Pattern.matches(regexPattern, pattern);
if(!isMatch){
// 不符合规则,不进行匹配
log.error("脱敏规则配置异常:{}", pattern);
throw new UnsupportedKeyWordsPattern();
}
String[] patternArr = pattern.split("-");
int startIndex = Integer.parseInt(patternArr[0]);
int endIndex = Integer.parseInt(patternArr[1]);
if(startIndex > endIndex){
log.error("脱敏规则配置异常:{}", pattern);
throw new UnsupportedKeyWordsPattern();
}
int replaceLength = endIndex - startIndex;
String replaceStr = "*";
for (int j = 1; j < replaceLength; j++) {
replaceStr += REPLACE_CHAR;
}
return valStr.substring(0,startIndex) + replaceStr + valStr.substring(endIndex);
}
/**
*
* 是否复杂数据对象 - 该属性不是 int、byte、long、double、float、char、boolean、Decimal、String 类型
* @param classSimpleName 数据类型
* @date 2020/12/25 14:28
* @author wei.heng
*/
public static boolean isComplexType(String classSimpleName) {
return !classSimpleName.equals("int") &&
!classSimpleName.equals("Integer") &&
!classSimpleName.equals("Byte") &&
!classSimpleName.equals("byte") &&
!classSimpleName.equals("Long") &&
!classSimpleName.equals("long") &&
!classSimpleName.equals("Double") &&
!classSimpleName.equals("double") &&
!classSimpleName.equals("Float") &&
!classSimpleName.equals("float") &&
!classSimpleName.equals("Character") &&
!classSimpleName.equals("char") &&
!classSimpleName.equals("Short") &&
!classSimpleName.equals("short") &&
!classSimpleName.equals("Boolean") &&
!classSimpleName.equals("boolean") &&
!classSimpleName.equals("BigDecimal") &&
!classSimpleName.equals("Date") &&
!classSimpleName.equals("String");
}
// public static void main(String[] args) {
//
// // 测试场景1 - object:
// AppUser appUser = new AppUser() {{
// setIdNumber("500225198810010011");
// }};
// new KeyWordsFilterUtil().filter(appUser, new KeyWordPatterns() {{
// addPattern("idNumber", "3-16");
// }});
// System.out.println("replacedInfo:" + JSON.toJSONString(appUser));
// // replacedInfo:{"idNumber":"500*************11"}
//
// // 测试场景2 - List:
// AppUser appUser2 = new AppUser() {{
// setIdNumber("511225198810010011");
// }};
// List<AppUser> objects = new ArrayList<>();
// objects.add(appUser);
// objects.add(appUser2);
// new KeyWordsFilterUtil().filter(objects, new KeyWordPatterns() {{
// addPattern("idNumber", "3-16");
// }});
// System.out.println("replacedInfo2:" + JSON.toJSONString(objects));
// // replacedInfo2:[{"idNumber":"500*************11"},{"idNumber":"511*************11"}]
//
// // 测试场景3 - 复杂对象
// appUser.setGasStudent(new GasStudent(){{
// setIdCard("500660198810010011");
// }});
// new KeyWordsFilterUtil().filter(objects, new KeyWordPatterns() {{
// addPattern("idNumber", "3-16");
// addPattern("idCard", "4-15");
// }});
// System.out.println("replacedInfo3:" + JSON.toJSONString(objects));
// //replacedInfo3:[{"gasStudent":{"idCard":"5006***********011"},"idNumber":"500*************11","sex":0},{"idNumber":"511*************11","sex":0}]
//
// // 测试场景4 - jsonObject
// JSONObject jsonObj = (JSONObject)JSONObject.toJSON(appUser);
// new KeyWordsFilterUtil().filter(jsonObj, new KeyWordPatterns() {{
// addPattern("idNumber", "3-16");
// addPattern("idCard", "4-15");
// }});
// System.out.println("replacedInfo jsonObj:" + JSON.toJSONString(jsonObj));
// // replacedInfo jsonObj:{"idNumber":"500*************11","gasStudent":{"idCard":"5006***********011"}}
//
// // 测试场景3 - map:
// // 这个不测了,不推荐走map参数...
// }
}