天天看點

Java脫敏工具類

前言:

好像沒有什麼要描述的…

就是關鍵資訊需要脫敏處理

設想是在網關的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參數...
//	}

}

           

繼續閱讀