使用 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 CustomizationLogstash 允许您自定义 JSON 输出以包含特定字段和元数据。

  • Dynamically Fields它还允许 动态添加字段 以根据应用程序上下文记录事件。

  • Improved ReadabilityJSON 格式为日志事件提供了清晰易读的结构。

  • Enhanced Search and Analysis日志聚合工具可以轻松解析和查询 JSON 日志。

  • Machine ParsingJSON 日志非常适合自动分析和警报系统。

屏蔽日志中数据的解决方案

这里的主要目标是提供一种解决方案来屏蔽数据,该解决方案在运行时是可定制的和可配置的。

这是我们的简单要求:

  1. 在日志中完全屏蔽密码。
  2. 屏蔽 Log 中除最后 5 个之外的电话号码和登录名。

步骤 1
创建 Spring Boot 应用程序。此解决方案适用于任何基于 Java 的应用程序,几乎不需要自定义。

步骤 2
配置所有正则表达式以屏蔽数据。请记住,正则表达式在资源利用方面成本高昂。确保你正在调整你的正则表达式。正则表达式组将允许我们从字符串中选择所需的子字符串。

步骤 3
创建一个类并实现 。这个接口来自 logstash,允许我们在打印到 appender 之前自定义消息。 method 将为每个日志消息调用。MessageJsonProviderwriteTo

  • in 方法读取所有正则表达式并准备包含所有 .此方法 from 和 just 将启动的进程标记为 true。start()``LogMasker``MaskingRule``AbstractJsonProvider

  • MaskingRule将保存正则表达式模式和一个函数。此函数将替换日志中标识的字符串 from。

highlight 复制代码
<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 文件中
配置类。

highlight 复制代码
<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
运行应用程序。为简单起见,我采用了一个保存数据的字符串,并在应用程序启动时打印它。

highlight 复制代码
<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>
相关推荐
IT枫斗者5 分钟前
如何解决Java EasyExcel 导出报内存溢出
java·服务器·开发语言·网络·分布式·物联网
爱编程的小生6 分钟前
Easyexcel(4-模板文件)
java·excel
求积分不加C8 分钟前
Kafka怎么发送JAVA对象并在消费者端解析出JAVA对象--示例
java·分布式·kafka·linq
2401_8576363912 分钟前
实验室管理平台:Spring Boot技术构建
java·spring boot·后端
问窗16 分钟前
微服务中Spring boot的包扫描范围
java·spring boot·微服务
是程序喵呀32 分钟前
SpringMVC详解
java·spring·spring-mvc
疯一样的码农38 分钟前
Apache Maven 标准文件目录布局
java·maven·apache
Felix666yy1 小时前
设计模式之建造者模式
java
界面开发小八哥1 小时前
「Java EE开发指南」如何使用Visual JSF编辑器设计JSP?(一)
java·ide·java-ee·编辑器·myeclipse
先睡1 小时前
javaEE
java·java-ee