Log4j2 日志脱敏

时间:2025-03-22 22:23:50

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

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

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

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

1、重写rewrite方法

import ;
import ;
import ;
import ;
import .;
import .;
import .;
import .;
import .;
import ..Log4jLogEvent;
import .;
import .;
import .;
import ;
import ;
import ;
import ;
import ;

/**
 * 
 *  针对log4j2的日志脱敏实现方案,重写rewrite方法,调整日志内容
 * 
 * 需要在 引入此插件,插件名称为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 ((encryptionKeys)) {
			((encryptionKeyArrays));
		}
	}

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

		Log4jLogEvent log4jLogEvent = (Log4jLogEvent) logEvent;

		Message message = ();
		if ((message instanceof ParameterizedMessage)) {
			ParameterizedMessage parameterizedMessage = (ParameterizedMessage) message;

			Object[] params = ();
			if (params == null ||  <= 0) {
				return logEvent;
			}
			Object[] newParams = new Object[];
			for (int i = 0; i < ; 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((), newParams,
					());
			 builder = ().setMessage(m);
			return ();
		} else if (message instanceof SimpleMessage) {
			
			SimpleMessage newMessage = decryptionSimpleMessage((SimpleMessage) message);
			 builder = ().setMessage(newMessage);
			return ();
		} else {

			return logEvent;
		}

	}

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

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

		if (!(messsage)) {

			boolean isContain = ().anyMatch(key -> (messsage, key));

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

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

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

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

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

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

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

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

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

				}
				return new SimpleMessage(newMessage);
			}

		}

		return simpleMessage;

	}

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

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

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <properties>
        <!--设置日志文件存储路径为tomcat/logs/${APP_NAME}-->
        <Property name="LOG_FILE_PATH">${sys:}/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="" level="${LOG_LEVEL}">
    		<AppenderRef ref="Console" />
    	</logger>
		<logger name="" level="${LOG_LEVEL}">
    		<AppenderRef ref="rewrite" />
    	</logger>
    
        <Root level="info">
            <!-- <AppenderRef ref="Console" /> -->
            <!-- <AppenderRef ref="RollingInfoFile" /> -->
        </Root>
    </Loggers>
    
</configuration>

3、单元测试

	@
	public void testLog4j2JSONString(){
		
		 Runtime r = ();
	        ();//计算内存前先垃圾回收一次
	        long start = ();//开始Time
	        long startMem = (); // 开始Memory

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

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



import ;
import ;
import ;

import .;
import ;

/**
 * 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 ((encryptionKeys)) {
			((encryptionKeyArrays));
		}
	}
	
	public static StringMaskUtil instance(){
		return ;
	}
	
	/**
	 * 支持的格式
	 * 
	 * 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 (!(messsage)) {

			boolean isContain = ().anyMatch(key -> (messsage, key));

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

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

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

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

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

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

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

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

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

				}
			}

		}
		return newMessage;
	}
	
	
}

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

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