天天看点

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参数...
//	}

}

           

继续阅读