日志脱敏首先要搞清楚,影响的数据范围,是要全局支持日志脱敏,还是只针对部分代码。

如果涉及到敏感数据的业务代码较少,建议写个数据脱敏工具类,在打印日志的时候调用,灵活可靠,影响范围小。

一、第一种方案:全局方式

          针对log4j2的日志脱敏实现方案,重写rewrite方法,对敏感数据进行脱敏操作
 需要在log4j2.xml 引入此插件,插件名称为DataMaskingRewritePolicy。

1、重写rewrite方法

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.google.common.base.Objects;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.appender.rewrite.RewritePolicy;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
import org.apache.logging.log4j.core.impl.Log4jLogEvent;
import org.apache.logging.log4j.message.Message;
import org.apache.logging.log4j.message.ParameterizedMessage;
import org.apache.logging.log4j.message.SimpleMessage;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;

/**
 * 
 *  针对log4j2的日志脱敏实现方案,重写rewrite方法,调整日志内容
 * 
 * 需要在log4j2.xml 引入此插件,插件名称为DataMaskingRewritePolicy
 *
 */
@Plugin(name = "DataMaskingRewritePolicy", category = "Core", elementType = "rewritePolicy", printObject = true)
public class DataMaskingRewritePolicy implements RewritePolicy {

	/*
	 * 脱敏符号
	 */
	private static final String ASTERISK = "****";

	/*
	 * 引号
	 */
	private static final String QUOTATION_MARK = "\"";

	// 使用静态内部类创建对象,节省空间
	private static class StaticDataMaskingRewritePolicy {
		private static final DataMaskingRewritePolicy dataMaskingRewritePolicy = new DataMaskingRewritePolicy();
	}

	// 需要加密的字段配置数组
	private static final String[] encryptionKeyArrays = { "password", "expireYear", "expireMonth",
			"cvv" };
	// 将数组转换为集合,方便查找
	private static final List<String> encryptionKeys = new ArrayList<>();

	public DataMaskingRewritePolicy() {
		if (CollectionUtils.isEmpty(encryptionKeys)) {
			encryptionKeys.addAll(Arrays.asList(encryptionKeyArrays));
		}
	}

	/**
	 * 日志修改方法,可以对日志进行过滤,修改
	 *
	 * @param logEvent
	 * @return
	 */
	@Override
	public LogEvent rewrite(LogEvent logEvent) {
		if (!(logEvent instanceof Log4jLogEvent)) {
			return logEvent;
		}

		Log4jLogEvent log4jLogEvent = (Log4jLogEvent) logEvent;

		Message message = log4jLogEvent.getMessage();
		if ((message instanceof ParameterizedMessage)) {
			ParameterizedMessage parameterizedMessage = (ParameterizedMessage) message;

			Object[] params = parameterizedMessage.getParameters();
			if (params == null || params.length <= 0) {
				return logEvent;
			}
			Object[] newParams = new Object[params.length];
			for (int i = 0; i < params.length; i++) {
				try {
					if(params[i] instanceof JSONObject){
						JSONObject jsonObject = (JSONObject) params[i];
						// 处理json格式的日志
						newParams[i] = encryptionJson(jsonObject, encryptionKeys);
					} else{
						newParams[i] = params[i];
					}
			
				} catch (Exception e) {
					newParams[i] = params[i];
				}
			}

			ParameterizedMessage m = new ParameterizedMessage(parameterizedMessage.getFormat(), newParams,
					parameterizedMessage.getThrowable());
			Log4jLogEvent.Builder builder = log4jLogEvent.asBuilder().setMessage(m);
			return builder.build();
		} else if (message instanceof SimpleMessage) {
			
			SimpleMessage newMessage = decryptionSimpleMessage((SimpleMessage) message);
			Log4jLogEvent.Builder builder = log4jLogEvent.asBuilder().setMessage(newMessage);
			return builder.build();
		} else {

			return logEvent;
		}

	}

	/**
	 * 单例模式创建(静态内部类模式)
	 *
	 * @return
	 */
	@PluginFactory
	public static DataMaskingRewritePolicy createPolicy() {
		return StaticDataMaskingRewritePolicy.dataMaskingRewritePolicy;
	}

	private SimpleMessage decryptionSimpleMessage(SimpleMessage simpleMessage) {
		String messsage = simpleMessage.toString();
		String newMessage = messsage;

		if (!StringUtils.isEmpty(messsage)) {

			boolean isContain = encryptionKeys.stream().anyMatch(key -> StringUtils.contains(messsage, key));

			// 包含敏感词
			if (isContain) {

				for (String key : encryptionKeyArrays) {
					int keyLength = key.length();
					// 敏感词
					String targetStr = new String("<" + key + ">");
					StringBuffer targetSb = new StringBuffer(targetStr);
					int startIndex = newMessage.indexOf(targetStr);

					/*
					 * 如<password>123456</password>替换为 <password>****</password>
					 */
					if (startIndex > -1 && newMessage.indexOf(targetSb.append(ASTERISK).append("</").append(key).append(">").toString()) == -1) {
						int endIndex = newMessage.indexOf(targetStr);
						if (endIndex > -1) {
							newMessage = newMessage.substring(0, startIndex + keyLength + 2) + ASTERISK
									+ newMessage.substring(endIndex, newMessage.length());
						}
					}

					/*
					 * 如password:123456替换为password:****
					 */
					 targetStr = key + ":";
					if (newMessage.indexOf(targetStr) > -1 && newMessage.indexOf(targetStr + ASTERISK) == -1) {

						startIndex = newMessage.indexOf(targetStr) + keyLength + 1;
						String endMessage = newMessage.substring(startIndex, newMessage.length());
						int endIndex = endMessage.indexOf(",");
						if (endIndex > -1) {
							newMessage = newMessage.substring(0, startIndex) + ASTERISK
									+ endMessage.substring(endIndex, endMessage.length());
						} else if (endMessage.indexOf(",") == -1 && endMessage.indexOf("=") == -1) {
							newMessage = newMessage.substring(0, startIndex) + ASTERISK;
						}
					}

					/*
					 * 如password=123456替换为password=****
					 */
					if (newMessage.indexOf(key + "=") > -1 && newMessage.indexOf(key + "=" + ASTERISK) == -1) {

						startIndex = newMessage.indexOf(key + "=") + keyLength + 1;
						String endMessage = newMessage.substring(startIndex, newMessage.length());
						int endIndex = endMessage.indexOf(",");
						if (endIndex > -1) {
							newMessage = newMessage.substring(0, startIndex) + ASTERISK
									+ endMessage.substring(endIndex, endMessage.length());
						} else if (endMessage.indexOf(",") == -1 && endMessage.indexOf("=") == -1) {
							newMessage = newMessage.substring(0, startIndex) + ASTERISK;
						}
					}

					/*
					 * 如"password":"123456" 替换为"password":"****"
					 */
					String qmKey = QUOTATION_MARK + key + QUOTATION_MARK + ":";
					if (newMessage.indexOf(qmKey) > -1 && newMessage.indexOf(qmKey + ASTERISK) == -1) {

						startIndex = newMessage.indexOf(qmKey) + keyLength + 3;
						String endMessage = newMessage.substring(startIndex, newMessage.length());
						int endIndex = endMessage.indexOf(",");
						if (endIndex > -1) {
							newMessage = newMessage.substring(0, startIndex) + QUOTATION_MARK + ASTERISK
									+ QUOTATION_MARK + endMessage.substring(endIndex, endMessage.length());
						} else if (endMessage.indexOf(",") == -1 && endMessage.indexOf("=") == -1) {
							newMessage = newMessage.substring(0, startIndex) + QUOTATION_MARK + ASTERISK
									+ QUOTATION_MARK;
						}
					}

				}
				return new SimpleMessage(newMessage);
			}

		}

		return simpleMessage;

	}

	/**
	 * 处理日志,递归获取值
	 *
	 */
	private Object encryptionJson(Object object, List<String> encryptionKeys) {
		String jsonString = JSON.toJSONString(object);
		if (object instanceof JSONObject) {
			JSONObject json = JSON.parseObject(jsonString);
			boolean isContain = encryptionKeys.stream().anyMatch(key -> StringUtils.contains(jsonString, key));
			if (isContain) {
				// 判断当前字符串中有没有key值
				Set<String> keys = json.keySet();
				keys.forEach(key -> {
					boolean result = encryptionKeys.stream().anyMatch(ekey -> Objects.equal(ekey, key));
					if (result) {
						String value = json.getString(key);
						// 加密
						json.put(key, ASTERISK);
					} else {
						json.put(key, encryptionJson(json.get(key), encryptionKeys));
					}
				});
			}
			return json;
		} else if (object instanceof JSONArray) {
			JSONArray jsonArray = JSON.parseArray(jsonString);
			for (int i = 0; i < jsonArray.size(); i++) {
				JSONObject jsonObject = jsonArray.getJSONObject(i);
				// 转换
				jsonArray.set(i, encryptionJson(jsonObject, encryptionKeys));
			}
			return jsonArray;
		}
		return object;
	}
}

2、修改log4j2.xml配置,增加Rewrite重新日志配置

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <properties>
        <!--设置日志文件存储路径为tomcat/logs/${APP_NAME}-->
        <Property name="LOG_FILE_PATH">${sys:catalina.home}/logs/${APP_NAME}</Property>
        <!--设置日志输出格式-->
        <Property name="PATTERN_FORMAT">[%t][%d{yyyyMMdd HH:mm:ss}][%-5p][%c{3}:%L] - %m%n</Property>
        <Property name="LOG_LEVEL">info</Property>
    </properties>

    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
        
        	 <PatternLayout pattern="[Shop][%t][%d{yyyyMMdd HH:mm:ss}][%-5p][%c{3}:%L] - %m%n"/>
        </Console>
        
        <!-- 配置重写日志 -->
        <Rewrite name="rewrite">
            <DataMaskingRewritePolicy/>
            <AppenderRef ref="Console"/>
            <!-- 将catalina日志重写 -->
            <!-- <AppenderRef ref="Catalina"/> -->
        </Rewrite>
 
    </Appenders>

    <Loggers>
    	<logger name="org.springframework" level="${LOG_LEVEL}">
    		<AppenderRef ref="Console" />
    	</logger>
		<logger name="org.test" level="${LOG_LEVEL}">
    		<AppenderRef ref="rewrite" />
    	</logger>
    
        <Root level="info">
            <!-- <AppenderRef ref="Console" /> -->
            <!-- <AppenderRef ref="RollingInfoFile" /> -->
        </Root>
    </Loggers>
    
</configuration>

3、单元测试

@org.junit.Test
	public void testLog4j2JSONString(){
		
		 Runtime r = Runtime.getRuntime();
	        r.gc();//计算内存前先垃圾回收一次
	        long start = System.currentTimeMillis();//开始Time
	        long startMem = r.totalMemory(); // 开始Memory

		for (int i = 0; i < 1; i++) {
			logger.info("\"password\":\"123456\",\"expireYear\":\"2025\",\"expireMonth\":\"10\",\"cvv\":\"844\"");
		}
		
		//输出
        long endMem =r.freeMemory(); // 末尾Memory
        long end = System.currentTimeMillis();//末尾Time
        System.out.println("用时消耗: "+String.valueOf(end - start)+"ms");
        System.out.println("内存消耗: "+String.valueOf((startMem- endMem)/1024)+"KB");
	}

二、第二种方案:自定义脱敏工具类

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.apache.commons.lang3.StringUtils;
import org.springframework.util.CollectionUtils;

/**
 * String数据脱敏处理

 *
 */
public class StringMaskUtil {

	/*
	 * 脱敏符号
	 */
	private static final String ASTERISK = "****";

	
	// 需要脱敏的字段配置数组
	private static final String[] encryptionKeyArrays = { "password", "expireYear", "expireMonth","cvv","number"};

	/*
	 * 引号
	 */
	private static final String QUOTATION_MARK = "\"";


	// 将数组转换为集合,方便查找
	private static final List<String> encryptionKeys = new ArrayList<>();
	
	// 使用静态内部类创建对象,节省空间
	private static class  StaticStringMaskUtil {
		private static final StringMaskUtil stringMaskUtil = new StringMaskUtil();
	}
	
	public StringMaskUtil() {
		if (CollectionUtils.isEmpty(encryptionKeys)) {
			encryptionKeys.addAll(Arrays.asList(encryptionKeyArrays));
		}
	}
	
	public static StringMaskUtil instance(){
		return StaticStringMaskUtil.stringMaskUtil;
	}
	
	/**
	 * 支持的格式
	 * 
	 * 1、<key>value</key>
	 * 2、key=value,key1=value1
	 * 3、key:value,key1:value1
	 * 4、"key":"value","key1":"value1"
	 * 
	 * @param messsage
	 * @return
	 */
	public String mask(String messsage){
		
		String newMessage = messsage;
		if (!StringUtils.isEmpty(messsage)) {

			boolean isContain = encryptionKeys.stream().anyMatch(key -> StringUtils.contains(messsage, key));

			// 包含敏感词
			if (isContain) {

				for (String key : encryptionKeys) {
					int keyLength = key.length();
					// 敏感词
					String targetStr = new String("<" + key + ">");
					StringBuffer targetSb = new StringBuffer(targetStr);
					int startIndex = newMessage.indexOf(targetStr);

					/*
					 * 如<password>123456</password>替换为 <password>****</password>
					 */
					if (startIndex > -1 && newMessage.indexOf(targetSb.append(ASTERISK).append("</").append(key).append(">").toString()) == -1) {
						int endIndex = newMessage.indexOf("</" + key + ">");
						if (endIndex > -1) {
							newMessage = newMessage.substring(0, startIndex + keyLength + 2) + ASTERISK
									+ newMessage.substring(endIndex, newMessage.length());
						}
					}

					/*
					 * 如,password:123456替换为password:****
					 */
					 targetStr =","+ key + ":";
					if (newMessage.indexOf(targetStr) > -1 && newMessage.indexOf(targetStr + ASTERISK) == -1) {

						startIndex = newMessage.indexOf(targetStr) + keyLength + 2;
						String endMessage = newMessage.substring(startIndex, newMessage.length());
						int endIndex = endMessage.indexOf(",");
						if (endIndex > -1) {
							newMessage = newMessage.substring(0, startIndex) + ASTERISK
									+ endMessage.substring(endIndex, endMessage.length());
						} else if (endMessage.indexOf(",") == -1 && endMessage.indexOf("=") == -1) {
							newMessage = newMessage.substring(0, startIndex) + ASTERISK;
						}
					}else if(newMessage.startsWith(key + ":")){
						startIndex = keyLength + 1;
						String endMessage = newMessage.substring(startIndex, newMessage.length());
						int endIndex = endMessage.indexOf(",");
						if (endIndex > -1) {
							newMessage = newMessage.substring(0, startIndex) + ASTERISK
									+ endMessage.substring(endIndex, endMessage.length());
						} else if (endMessage.indexOf(",") == -1 && endMessage.indexOf("=") == -1) {
							newMessage = newMessage.substring(0, startIndex) + ASTERISK;
						}
					}

					/*
					 * 如,password=123456替换为password=****
					 */
					 targetStr =","+ key + "=";
					if (newMessage.indexOf(targetStr) > -1 && newMessage.indexOf(key + "=" + ASTERISK) == -1) {

						startIndex = newMessage.indexOf(targetStr) + keyLength + 2;
						String endMessage = newMessage.substring(startIndex, newMessage.length());
						int endIndex = endMessage.indexOf(",");
						if (endIndex > -1) {
							newMessage = newMessage.substring(0, startIndex) + ASTERISK
									+ endMessage.substring(endIndex, endMessage.length());
						} else if (endMessage.indexOf(",") == -1 && endMessage.indexOf("=") == -1) {
							newMessage = newMessage.substring(0, startIndex) + ASTERISK;
						}
					}else if(newMessage.startsWith(key + "=")){
						startIndex = keyLength + 1;
						String endMessage = newMessage.substring(startIndex, newMessage.length());
						int endIndex = endMessage.indexOf(",");
						if (endIndex > -1) {
							newMessage = newMessage.substring(0, startIndex) + ASTERISK
									+ endMessage.substring(endIndex, endMessage.length());
						} else if (endMessage.indexOf(",") == -1 && endMessage.indexOf("=") == -1) {
							newMessage = newMessage.substring(0, startIndex) + ASTERISK;
						}
					}

					/*
					 * 如"password":"123456" 替换为"password":"****"
					 */
					String qmKey = QUOTATION_MARK + key + QUOTATION_MARK + ":";
					if (newMessage.indexOf(qmKey) > -1 && newMessage.indexOf(qmKey + ASTERISK) == -1) {

						startIndex = newMessage.indexOf(qmKey) + keyLength + 3;
						String endMessage = newMessage.substring(startIndex, newMessage.length());
						int endIndex = endMessage.indexOf(",");
						if (endIndex > -1) {
							newMessage = newMessage.substring(0, startIndex) + QUOTATION_MARK + ASTERISK
									+ QUOTATION_MARK + endMessage.substring(endIndex, endMessage.length());
						} else if (endMessage.indexOf(",") == -1 && endMessage.indexOf("=") == -1) {
							newMessage = newMessage.substring(0, startIndex) + QUOTATION_MARK + ASTERISK
									+ QUOTATION_MARK;
						}
					}

				}
			}

		}
		return newMessage;
	}
	
	
}

自定义脱敏工具类单元测试

@org.junit.Test
	public void testLog4jString(){
		
		Runtime r = Runtime.getRuntime();
        r.gc();//计算内存前先垃圾回收一次
        long start = System.currentTimeMillis();//开始Time
        long startMem = r.totalMemory(); // 开始Memory
		
		for (int i = 0; i < 1; i++) {
			System.out.println(StringMaskUtil.instance().mask("oldpassword=123456,expireYear=2022,expireMonth=12,cvv=844"));
			System.out.println(StringMaskUtil.instance().mask("password:123456,aaexpireYear:2022,expireMonth:12,cvv:844"));
			System.out.println(StringMaskUtil.instance().mask("password=123456,expireYear=2022,expireMonth=12,cvv=844"));
			System.out.println(StringMaskUtil.instance().mask("\"oldpassword\":\"123456\",\"expireYear\":\"2025\",\"expireMonth\":\"10\",\"cvv\":\"844\""));
			System.out.println(StringMaskUtil.instance().mask("<password>123456</password><expireYear>123456</expireYear><expireMonth>123456</expireMonth><cvv>123456</cvv>"));
		}
		//输出
        long endMem =r.freeMemory(); // 末尾Memory
        long end = System.currentTimeMillis();//末尾Time
        System.out.println("用时消耗: "+String.valueOf(end - start)+"ms");
        System.out.println("内存消耗: "+String.valueOf((startMem- endMem)/1024)+"KB");
	}