java 中用 Logback 屏蔽敏感数据

屏蔽日志回放中的敏感数据是通过用一些任意的编码文本部分或全部**替换客户敏感数据或NPI(非公开个人信息)**来实现的。例如,SSN信息可以被替换成所有的星形字符,或者我们可以从日志中删除完整的SSN信息。

1.屏蔽日志中的NPI

一般来说,我们可以通过两种方式屏蔽敏感数据。

第一种方法(不推荐)是创建一些实用的函数,为具有敏感信息的领域对象创建屏蔽的字符串表示。

Logger.info("Transaction completed with details : " + CommonUtils.mask(trasaction));
复制代码

这种方法是有问题的,因为屏蔽调用分散在所有的应用程序代码中。在未来,我们被要求只在生产和预生产环境中屏蔽数据,那么我们可能会在多个地方改变代码。

同样地,如果我们发现在屏蔽过程中漏掉了一个领域对象,那么我们可能需要在很多地方和很多日志语句中改变代码。

第二种方法是将屏蔽逻辑与应用程序代码分开,并将其放在Logback配置中。现在,屏蔽逻辑的变化将是配置文件和布局处理程序的核心。应用程序类将不参与任何形式的屏蔽逻辑。

屏蔽逻辑或范围的任何变化必须由Logback通过布局处理程序类和配置文件来处理。这个选项很容易被管理,这应该是日志中数据屏蔽的首选方式

2.如何用Logback屏蔽数据

Logback中的数据屏蔽分两步完成。

  1. logback.xml配置文件中借助正则表达式定义屏蔽模式。
  2. 定义一个自定义的Layout类,该类将读取屏蔽模式,并在日志信息中应用这些模式的正则表达式。

2.1.配置文件中的屏蔽模式

这是一个略微困难的部分,你将为要屏蔽的信息编写regex模式。编写正则表达式来覆盖各种格式的输出可能不是那么容易,但一旦完成,你以后会感谢自己。

下面是这样一个配置,使用控制台appender(用于演示)记录屏蔽数据,*它只屏蔽了电子邮件SSN*字段。

<appender name="DATA_MASK" class="ch.qos.logback.core.ConsoleAppender">
    <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
       <layout class="com.howtodoinjava.demo.logback.DataMaskingPatternLayout">
       <maskPattern>((?!000|666)[0-8][0-9]{2}-(?!00)[0-9]{2}-(?!0000)[0-9]{4})</maskPattern> <!-- SSN -->
       <maskPattern>(\w+@\w+\.\w+)</maskPattern> <!-- Email -->
       <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
       </layout>
    </encoder>
</appender>
复制代码

请注意,通过使用Janino库的if-else条件,我们可以很容易地在特定的环境中启用或禁用屏蔽功能。

<dependency>
    <groupId>org.codehaus.janino</groupId>
    <artifactId>janino</artifactId>
    <version>3.1.6</version>
</dependency>
复制代码

在给定的例子中,我们在生产环境中启用了数据屏蔽,在所有其他环境中禁用了它。ENV 是一个系统属性,它返回应用程序正在运行的环境名称。

<if condition='property("ENV").equals("prod")'>
	<then>
	<appender name="DATA_MASK" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
           <layout class="com.howtodoinjava.demo.logback.DataMaskingPatternLayout">
		       <maskPattern>((?!000|666)[0-8][0-9]{2}-(?!00)[0-9]{2}-(?!0000)[0-9]{4})</maskPattern> <!-- SSN -->
		       <maskPattern>(\w+@\w+\.\w+)</maskPattern> <!-- Email -->
		       <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
           </layout>
        </encoder>
    </appender>
  </then>
  <else>
  	<appender name="DATA_MASK" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
			<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
		</encoder>
    </appender>
  </else>
</if>
复制代码

2.2.自定义PatternLayout

解决方案的第二部分是从配置中读取屏蔽模式,并在日志信息中应用它们。这是一个相当简单的方法,可以通过一个自定义模式处理程序来实现。

给定的模式处理程序通过结合配置中的所有模式并使用OR运算符创建了一个单一的正则表达式。这个模式被应用于所有需要被这个模式处理程序处理的日志消息。

我们可以定制这个处理程序中实现的逻辑,以满足我们自己的要求。

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import ch.qos.logback.classic.PatternLayout;
import ch.qos.logback.classic.spi.ILoggingEvent;

public class DataMaskingPatternLayout extends PatternLayout 
{
	private Pattern aplpliedPattern;
	private List<String> maskPatterns = new ArrayList<>();

	public void addMaskPattern(String maskPattern) {
		maskPatterns.add(maskPattern);
		aplpliedPattern = Pattern.compile( maskPatterns.stream()
					.collect(Collectors.joining("|")), Pattern.MULTILINE);
	}

	@Override
	public String doLayout(ILoggingEvent event) {
		return maskMessage(super.doLayout(event));
	}

	private String maskMessage(String message) {
		//When masking is disabled in a environment
		if (aplpliedPattern == null) {
			return message;
		}
		StringBuilder sb = new StringBuilder(message);
		Matcher matcher = aplpliedPattern.matcher(sb);
		while (matcher.find()) {
			IntStream.rangeClosed(1, matcher.groupCount()).forEach(group -> {
				if (matcher.group(group) != null) {
					IntStream.range(matcher.start(group), 
								matcher.end(group)).forEach(i -> sb.setCharAt(i, '*'));
				}
			});
		}
		return sb.toString();
	}
}
复制代码

3.演示

让我们看看数据屏蔽的实际效果。我将在生产和非生产模式下执行演示代码。

非生产模式下,我们没有设置系统属性ENV,所以数据屏蔽不会发生。

Logger logger = LoggerFactory.getLogger(Main.class);

Map<String, String> customer = new HashMap<String, String>();
customer.put("id", "12345");
customer.put("ssn", "856-45-6789");
customer.put("email", "[email protected]");

logger.info("Customer found : {}", new JSONObject(customer));
复制代码
21:02:18.683 [main] INFO  com.howtodoinjava.demo.slf4j.Main - Customer found : {"id":"12345","email":"[email protected]","ssn":"856-45-6789"}
复制代码

当我们在生产模式下运行应用程序时,我们可以看到被屏蔽的输出。

//Production mode ON
System.setProperty("ENV", "prod");

Logger logger = LoggerFactory.getLogger(Main.class);

Map<String, String> customer = new HashMap<String, String>();
customer.put("id", "12345");
customer.put("ssn", "856-45-6789");
customer.put("email", "[email protected]");

logger.info("Customer found : {}", new JSONObject(customer));
复制代码
21:03:07.960 [main] INFO  com.howtodoinjava.demo.slf4j.Main - Customer found : {"id":"12345","email":"***************","ssn":"***********"}
复制代码

4.结论

在这个Logback教程中,我们学会了 创建自定义PatternLayout来屏蔽敏感数据从应用程序的日志。数据屏蔽模式是由配置文件集中控制的,这使得这项技术非常有用。

我们可以通过使用Logback隐式支持的Janino库中的条件标签来扩展这个功能,以实现特定环境的屏蔽。

学习愉快!!

下载源代码

Guess you like

Origin juejin.im/post/7058838575776792606