前言:
好像沒有什麼要描述的…
就是關鍵資訊需要脫敏處理
設想是在網關的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參數...
// }
}