使用 Regex 在 Java 中使用 Logstash LogBack 屏蔽日志
在当今数据驱动的世界中,数据安全至关重要。日志记录框架在应用程序监控和调试中起着至关重要的作用,但它们可能会无意中暴露本不应该暴露的敏感信息。日志掩码是一种可以有效地混淆日志消息中的敏感数据以保护机密信息的技术。
了解 Logback
Logback 是 Java 应用程序中功能强大且最常用的日志记录框架。它提供灵活的配置选项,包括将日志事件格式化为 JSON 对象的能力。它是 Log4j 框架的继任者,由于其功能和易用性而迅速流行起来。它由 Logger、Encoders、Layout、Appender、Encoder 组成。
记录:Logger 是日志消息的上下文。应用程序将与此类交互以创建日志消息。
编码:编码器在 0.9.91 中引入,负责将事件转换为字节数组以及将该字节数组写出为 .作为 Layouts 引入的编码器只能将事件转换为 String,从而将其范围限制为非二进制输出。logbackOutputStream
布局:布局负责根据用户的意愿格式化 logging request,而 appender 负责将格式化的输出发送到其目的地。
附加程序:用 logback 的话来说,输出目的地称为 appender。这会将日志消息放置在其最终目标中。一个 Logger 可以有多个 Appender。目前,存在用于控制台、文件、远程套接字服务器、MySQL、PostgreSQL、Oracle 和其他数据库、JMS 和远程 UNIX Syslog 守护进程的附加程序。
关于 Logstash Logback Encoder
logstash-logback-encoder库是增强 Spring Boot 应用程序的日志记录功能的宝贵工具。它提供了一种将日志消息格式化为结构化 JSON 格式的便捷方法,使其易于 Logstash 等日志聚合和分析工具使用。JSON 格式提供了一种结构化和机器可读的方式来记录信息,使其成为高级日志分析和安全措施的理想选择。Logstash 的优势
-
JSON Customization
Logstash 允许您自定义 JSON 输出以包含特定字段和元数据。 -
Dynamically Fields
它还允许 动态添加字段 以根据应用程序上下文记录事件。 -
Improved Readability
JSON 格式为日志事件提供了清晰易读的结构。 -
Enhanced Search and Analysis
日志聚合工具可以轻松解析和查询 JSON 日志。 -
Machine Parsing
JSON 日志非常适合自动分析和警报系统。
屏蔽日志中数据的解决方案
这里的主要目标是提供一种解决方案来屏蔽数据,该解决方案在运行时是可定制的和可配置的。
这是我们的简单要求:
- 在日志中完全屏蔽密码。
- 屏蔽 Log 中除最后 5 个之外的电话号码和登录名。
步骤 1
创建 Spring Boot 应用程序。此解决方案适用于任何基于 Java 的应用程序,几乎不需要自定义。
步骤 2
配置所有正则表达式以屏蔽数据。请记住,正则表达式在资源利用方面成本高昂。确保你正在调整你的正则表达式。正则表达式组将允许我们从字符串中选择所需的子字符串。
步骤 3
创建一个类并实现 。这个接口来自 logstash,允许我们在打印到 appender 之前自定义消息。 method 将为每个日志消息调用。MessageJsonProviderwriteTo
-
in 方法读取所有正则表达式并准备包含所有 .此方法 from 和 just 将启动的进程标记为 true。
start()
LogMasker
MaskingRule
AbstractJsonProvider
-
MaskingRule
将保存正则表达式模式和一个函数。此函数将替换日志中标识的字符串 from。
<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>@Data
public class MaskingMessagingProvider extends MessageJsonProvider {
public static final String DEFAULT_RULES_DELIMITER = ",";
private LogMasker logMasker;
private String rules;
public MaskingMessagingProvider() {
super();
}
@Override
public void start() {
super.start();
this.logMasker = LogMasker.create(StringUtils.tokenizeToStringArray(rules, DEFAULT_RULES_DELIMITER));
}
@Override
public void writeTo(JsonGenerator generator, ILoggingEvent event) throws IOException {
if (isStarted()) {
JsonWritingUtils.writeStringField(generator, getFieldName(), logMasker.mask(event.getFormattedMessage()));
}
}
}
class LogMasker {
private MaskingRule[] masks;
public LogMasker(MaskingRule[] masks) {
super();
this.masks = masks.clone();
}
public static LogMasker create(String[] rules) {
return new LogMasker(Arrays.stream(rules).map(rule -> MaskingRule.create(rule)).toArray(MaskingRule[]::new));
}
public String mask(String input) {
String transformed = input;
for (MaskingRule m : masks) {
transformed = m.mask(transformed);
}
return transformed;
}
}
class MaskingRule {
public static final int REG_EX_DEFAULT_GROUP_SELECTOR = 2;
public static final String DEFAULT_REPLACEMENT = "*";
private Pattern pattern;
private UnaryOperator<String> replacement;
public MaskingRule(Pattern maskPattern, UnaryOperator<String> replacement) {
super();
this.pattern = maskPattern;
this.replacement = replacement;
}
public static MaskingRule create(String rule) {
return new MaskingRule(Pattern.compile(rule), (in) -> MaskingRule.maskDataWithReplacement(in, DEFAULT_REPLACEMENT));
}
public String mask(String transformed) {
Matcher matcher = pattern.matcher(transformed);
StringBuffer sb = new StringBuffer();
while (matcher.find()) {
matcher.appendReplacement(sb, replacement.apply(getDataToBeMasked(matcher)));
}
matcher.appendTail(sb);
return sb.toString();
}
private static String maskDataWithReplacement(String input, String replacement) {
int repetition = !StringUtils.hasLength(input) ? 0 : input.length();
return String.join("", Collections.nCopies(repetition, replacement));
}
private static String getDataToBeMasked(Matcher matcher) {
if (matcher.groupCount() > 1) {
return matcher.group(REG_EX_DEFAULT_GROUP_SELECTOR);
}
return matcher.groupCount() > 0 ? matcher.group(1) : "";
}
}
</code></span></span>
步骤 4 在 logback-spring.xml 文件中
配置类。
<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code><configuration>
<springProperty scope="context" name="rules" source="app.logging.masking.rules"
defaultValue=""/>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
<providers>
<provider class="com.daya.logging.logstash.MaskingMessagingProvider">
<rules>${rules}</rules>
<rulesDelimiter>${rulesDelimiter}</rulesDelimiter>
<ruleDelimiter>${ruleDelimiter}</ruleDelimiter>
</provider>
<threadName/>
<timestamp/>
<logLevel/>
<loggerName/>
<mdc/>
<version/>
<stackTrace/>
</providers>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
</root>
</configuration>
</code></span></span>
步骤 5
运行应用程序。为简单起见,我采用了一个保存数据的字符串,并在应用程序启动时打印它。
<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>@SpringBootApplication
@Slf4j
public class LogDataMaskingApplication {
public static void main(String[] args) {
SpringApplication.run(LogDataMaskingApplication.class, args);
LogDataMaskingApplication.maskingTest();
}
public static void maskingTest() {
String data = "{\"loginName\":\"maskingtest\",\"phoneNumber\":\"9898981212\",\"password\":\"Masking@123\"}";
log.info(data);
}
}
</code></span></span>