Java日志脱敏——基于logback MessageConverter实现

news2024/11/26 18:37:32

背景简介

日志脱敏 是常见的安全需求,最近公司也需要将这一块内容进行推进。看了一圈网上的案例,很少有既轻量又好用的轮子可以让我直接使用。我一直是反对过度设计的,而同样我认为轮子就应该是可以让人拿去直接用的。所以我准备分享两篇博客分别实现两种日志脱敏方案。

方案分析

  • logback MessageConverter + 正则匹配本篇博客主要介绍此方法

    • 优势
      • 侵入性低、工作量极少, 只需要修改xml配置文件,适合老项目
    • 劣势
      • 效率低,会对每一行日志都进行正则匹配检查,效率受日志长度影响,日志越长效率越低,影响日志吞吐量
      • 因基于正则匹配 存在错杀风险,部分内容难以准确识别
  • fastjson Filter + 注解 + 工具类下一篇博客介绍 传送门:Java日志脱敏(二)

    • 优势
      • 性能损耗低、效率高、扩展性强,精准脱敏,适合QPS较高日志吞吐量较大的项目。
    • 劣势
      • 侵入性较高,需对所有可能的情况进行脱敏判断
      • 存在漏杀风险,全靠开发控制

其实还有一种方案,基于 工具类+配置模式
优势是 工作量低(比注解模式低,比正则匹配模式高),灵活度高,性能也好。但是只适合那些新项目,如果是老项目大家命名不规范,就很难推动整改了。此处不进行扩展。详见:项目日志脱敏

logback MessageConverter + 正则匹配

流程图解

在这里插入图片描述

代码案例

正则匹配日志脱敏工具类

此工具类主要用于实现依据配置的正则匹配规则集,进行依次匹配。并提取敏感文本对其执行对应的脱敏策略。大家拿去用可以不做修改

package com.zhibo.log.format;

import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
 * @Author: Zhibo.lv
 * @Description: 正则匹配日志脱敏工具类
 **/
@Component
public class LogSensitiveUtils {
    // 脱敏日志最大长度,超出此长度的日志放弃脱敏,直接返回
    private static Integer SENSITIVE_LOG_MAX_LENGTH = 10000;

    /**
     * 日志脱敏 获取规则集进行依次匹配
     * @param content 明文日志文本
     * @return 脱敏后的日志文本
     */
    public static String filterSensitive(String content) {
        try {
            if (StringUtils.isNotBlank(content) && content.length() < SENSITIVE_LOG_MAX_LENGTH) {
                for (Map.Entry<String, List<Pattern>> entry : LogSensitiveConstants.SENSITIVE_SEQUENCE.entrySet()) {
                    content = filter(content, entry.getKey(), entry.getValue());
                }
            }
            return content;
        } catch (Exception e) {
            return content;
        }
    }

    /**
     *
     * @param content   需脱敏字符串
     * @param type      文本类型,依据类型可以做不同的脱敏方式
     * @param patterns  该方式下需匹配的正则
     * @return
     *
     */
    public static String filter(String content, String type, List<Pattern> patterns) {
        for (Pattern pattern : patterns) {
            Matcher matcher = pattern.matcher(content);
            StringBuffer sb = new StringBuffer();
            while (matcher.find()) {
                matcher.appendReplacement(sb, Matcher.quoteReplacement(baseSensitive(matcher.group(), type)));
            }
            matcher.appendTail(sb);
            content = sb.toString();
        }
        return content;
    }

    /**
     * 依据正则抓去的文本执行对应的脱敏策略
     * @param str 待脱敏的字符串
     * @return
     */
    private static String baseSensitive(String str, String type) {
        if (StringUtils.isBlank(str)) {
            return StringUtils.EMPTY;
        }
		//通过工厂获取对应类型的脱敏类执行脱敏方法
        return SensitiveStrategyBuiltInUtil.getStrategy(type).des(str);
    }
}
正则匹配日志脱敏常量

此工具类主要是进行配置需要脱敏的文本的正则。需要大家依据业务调整或新增

package com.zhibo.log.format;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.regex.Pattern;

/**
 * @Author: Zhibo
 * @Description: 正则匹配日志脱敏常量
 **/
public class LogSensitiveConstants {

    /**
     * 过滤先后顺序:身份证 -> 手机号
     * 顺序原因:避免部分业务需求出现可能同时满足多个正则规则的文本,大家可以优先提取更长的、更复杂的文本。后处理简单的
     */
    public static final Map<String,List<Pattern>> SENSITIVE_SEQUENCE = new TreeMap<String, List<Pattern>>();

    /**
     * 手机号匹配规则集,支持配置多个正则规则
     */
    public static final List<Pattern> SENSITIVE_PHONE_KEY = new ArrayList<Pattern>(1);

    /**
     * 身份证号码匹配规则集,支持配置多个正则规则
     */
    public static final List<Pattern> SENSITIVE_ID_NO_KEY = new ArrayList<Pattern>(1);


    /**
     * 手机号正则匹配,11位1开头数字
     * 瞻前顾后:校验符合要求的文本前后均不能为数字 避免误匹配
     */
    public static final String PHONE_REGEX = "(?<!\\d)[1][3-9][0-9]{9}(?!\\d)";

    /**
     * 身份证号正则匹配 18位数版本
     * 15位数的身份证号码暂不考虑,如果需要自行新增下方正则加入 SENSITIVE_ID_NO_KEY 中
     * (?<!\d)([1-9]\d{7}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3})(?!\d)
     * 瞻前顾后:校验符合要求的文本前后均不能为数字 避免误匹配
     */
    public static final String ID_NO_REGEX = "(?<!\\d)([1-9]\\d{5}[1-9]\\d{3}((0\\d)|(1[0-2]))(([0|1|2]\\d)|3[0-1])\\d{3}([0-9]|X))(?!\\d)";

    static {
        SENSITIVE_ID_NO_KEY.add(Pattern.compile(ID_NO_REGEX));
        SENSITIVE_PHONE_KEY.add(Pattern.compile(PHONE_REGEX));
    }
    
	// 脱敏替代字符
	public static final char STAR = '*';

	// 手机号类型脱敏替代字符
    public static final String PHONE_MASK = "****";

    /** 手机号码脱敏策略 */
    public static final String STRATEGY_PHONE = "strategyPhone";

    /** 身份证号码脱敏策略 */
    public static final String STRATEGY_ID_NO = "strategyIdNo";

    static {
    	//将每一个规则集绑定一个对应的类型
        SENSITIVE_SEQUENCE.put(STRATEGY_ID_NO, SENSITIVE_ID_NO_KEY);
        SENSITIVE_SEQUENCE.put(STRATEGY_PHONE, SENSITIVE_PHONE_KEY);
    }

    private LogSensitiveConstants() {
    }
}
脱敏策略代码

定义文本脱敏接口 IStrategy

package com.zhibo.log.sensitive.api;

/**
 * @Author: Zhibo
 * @Description: 脱敏策略
 */
public interface IStrategy {
    /**
     * 脱敏
     * @param original 原始内容
     * @return 脱敏后的字符串
     */
    String des(final Object original);
}

文本脱敏抽象类,进行通用实现 AbstractStringStrategy

package com.zhibo.log.sensitive.core.strategory;

import com.zhibo.log.sensitive.api.IStrategy;
import com.zhibo.log.format.LogSensitiveConstants;

import java.security.MessageDigest;


/**
 * @Author: zhibo
 * @Description: 抽象字符串策略,
 * 				支持在脱敏后的文本后面追加明文的MD5加密串,方便研发进行日志查询使用
 */
public abstract class AbstractStringStrategy implements IStrategy {

    /**
     * 获取掩码之前的长度
     * @param original 原始
     * @param chars 字符串
     * @return 结果
     */
    protected abstract int getBeforeMaskLen(Object original, char[] chars);

    /**
     * 获取掩码之后的长度
     * @param original 原始
     * @param chars 字符串
     * @return 结果
     */
    protected abstract int getAfterMaskLen(Object original, char[] chars);

    /**
     * 针对固定长度的加密直接返回脱敏字符串,避免StringBuilder循环拼接
     * @return 脱敏字符串
     *      如返回null 则通过 {@link AbstractStringStrategy#getBeforeMaskLen(Object, char[])} 与 {@link AbstractStringStrategy#getAfterMaskLen(Object, char[])}
     *      进行截取字符串
     */
    protected String getMask(){
        return null;
    }

    /**
     * 是否需要拼接MD5密文方便日志查询。
     * @return false : 不拼接(默认)
     *          true :  拼接密文 用于日志查询 格式  [MD5]
     */
    protected Boolean addMD5(){
        return false;
    }

    

    @Override
    public String des(Object original) {
        if(original == null) {
            return null;
        }

        String strValue = original.toString();
        char[] chars = strValue.toCharArray();
        int beforeMaskLen = getBeforeMaskLen(original, chars);
        int afterMaskLen = getAfterMaskLen(original, chars);

        //范围纠正
        int maxLen = chars.length;
        beforeMaskLen = Math.min(beforeMaskLen, maxLen);
        afterMaskLen = Math.min(afterMaskLen, maxLen);
        StringBuilder stringBuilder = new StringBuilder();

        //获取明文前缀
        if(beforeMaskLen > 0) {
            stringBuilder.append(chars, 0, beforeMaskLen);
        }
        //获取脱敏字符串
        String mask = getMask();
        if (null == mask){//如未指定脱敏字符串则按规则循环拼接
            // 中间使用掩码
            for(int i = beforeMaskLen; i < chars.length - afterMaskLen; i++) {
                stringBuilder.append(LogSensitiveConstants.STAR);
            }
        }else {
            stringBuilder.append(mask);
        }

        //获取明文后缀
        if(afterMaskLen > 0) {
            stringBuilder.append(chars, chars.length - afterMaskLen, afterMaskLen);
        }

        if (addMD5()){
            addMD5(strValue,stringBuilder);
        }
        return stringBuilder.toString();
    }
    
    // MD5加密
	private void addMD5(String originalString,StringBuilder stringBuilder) {
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            md.update(originalString.getBytes());
            byte[] digest = md.digest();
            stringBuilder.append("[");
            for (byte b : digest) {
                stringBuilder.append(String.format("%02x", b));
            }
            stringBuilder.append("]");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

身份证脱敏策略实现 StrategyIdNo

package com.zhibo.log.sensitive.core.strategory;

/**
 * @Author: Zhibo
 * @Description: 身份证号脱敏
 * 脱敏规则:保留前6 后4 位,其它由星号替换
 */
public class StrategyIdNo extends AbstractStringStrategy {

    @Override
    protected int getBeforeMaskLen(Object original, char[] chars) {
        return 6;
    }

    @Override
    protected int getAfterMaskLen(Object original, char[] chars) {
        return 4;
    }
}

手机号码脱敏策略实现 StrategyPhone

package com.zhibo.log.sensitive.core.strategory;

import com.zhibo.log.format.LogSensitiveConstants;

/**
 * @Author: zhibo
 * @Description: 手机号脱敏
 * 脱敏规则:186****8567[MD5]
 */
public class StrategyPhone extends AbstractStringStrategy {

    @Override
    protected int getBeforeMaskLen(Object original, char[] chars) {
        return 3;
    }

    @Override
    protected int getAfterMaskLen(Object original, char[] chars) {
        return 4;
    }

    @Override
    protected String getMask() {
        return LogSensitiveConstants.PHONE_MASK;
    }

    @Override
    protected Boolean addMD5(){
        return true;
    }
}
logback 消息转换器实现

最关键的方法来啦

package com.zhibo.log.format;

import ch.qos.logback.classic.pattern.MessageConverter;
import ch.qos.logback.classic.spi.ILoggingEvent;

/**
 * @Author: Zhibo
 * @Description: 日志脱敏转换器
 **/
public class SensitiveConverter extends MessageConverter {
    @Override
    public String convert(ILoggingEvent event){
        // 获取原始日志
        String requestLogMsg = super.convert(event);
        // 执行日志脱敏
        return LogSensitiveUtils.filterSensitive(requestLogMsg);
    }

    public SensitiveConverter() {
    }
}

自此我们的工具包也就完成了,业务系统需要使用此工具只需要修改resources目录下的logback.xml配置。

<!-- 新增或修改原有消息转换器为SensitiveConverter -->
<conversionRule conversionWord="msgToo" converterClass="com.zhibo.log.format.SensitiveConverter" />

并将文件输出日志的消息内容替换为指定消息转换器的 conversionWord
在这里插入图片描述

脱敏效果展示

在这里插入图片描述

有请提示

注:此方法对日志吞吐量存在影响,由于正则需要循环匹配整个日志文本,所以正则规则越多,日志文本越长,耗时越长。如您的应用程序对日志吞吐量要求较高且存在大量超长日志文本请压测后使用。

如配置了logback的异步打印,且设置了允许日志丢弃,在压测中可能出现因线程池与等待队列均被占满而导致日志丢失情况。下面是我的问题复盘:

logback日志异步打印配置如下

<appender name="ASYNC-FILE" class="ch.qos.logback.classic.AsyncAppender">
		<neverBlock>true</neverBlock><!-- 非阻塞方式运行 如队列满就开始丢弃日志 -->
		<queueSize>1024</queueSize><!-- 等待队列大小 -->
		<discardingThreshold>0</discardingThreshold><!-- 日志队列深度,配置0 队列满后丢弃最老的日志 -->
		<appender-ref ref="FILE"/>
</appender>

以上配置 为logback线程池工作配置,默认线程池 线程数为 10个,最大队列长度为1024个。
意味着如果日志产生的速度超过10个线程工作处理日志的速度,则无法处理的日志会被写入BlockingQueue 队列,当队列满了之后就会导致日志丢失的情况。

继续阅读:Java日志脱敏(二)——fastjson Filter + 注解 + 工具类实现

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2232001.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java面试经典 150 题.P26. 删除有序数组中的重复项(003)

本题来自&#xff1a;力扣-面试经典 150 题 面试经典 150 题 - 学习计划 - 力扣&#xff08;LeetCode&#xff09;全球极客挚爱的技术成长平台https://leetcode.cn/studyplan/top-interview-150/ 题解&#xff1a; class Solution {public int removeDuplicates(int[] nums) …

Prometheus套装部署到K8S+Dashboard部署详解

1、添加helm源并更新 helm repo add prometheus-community https://prometheus-community.github.io/helm-charts helm repo update2、创建namespace kubectl create namespace monitoring 3、安装Prometheus监控套装 helm install prometheus prometheus-community/prome…

如何选择到印尼的海运代理

如何选择到印尼的海运代理 选择合适的海运代理的重要性 海运代理负责安排货物从发货地到目的地的整个运输过程&#xff0c;包括装运、清关、仓储等服务。一个可靠的海运代理能确保货物安全准时到达&#xff0c;并帮助企业节省时间和成本。 选择海运代理需考虑的主要因素 公司…

python常用的第三方库下载方法

方法一&#xff1a;打开pycharm-打开项目-点击左侧图标查看已下载的第三方库-没有下载搜索后点击install即可直接安装--安装成功后会显示在installed列表 方法二&#xff1a;打开dos窗口输入命令“pip install requests“后按回车键&#xff0c;看到successfully既安装成功&…

FFmpeg 4.3 音视频-多路H265监控录放C++开发八,使用SDLVSQT显示yuv文件 ,使用ffmpeg的AVFrame

一. AVFrame 核心回顾&#xff0c;uint8_t *data[AV_NUM_DATA_POINTERS] 和 int linesize[AV_NUM_DATA_POINTERS] AVFrame 存储的是解码后的数据&#xff0c;&#xff08;包括音频和视频&#xff09;例如&#xff1a;yuv数据&#xff0c;或者pcm数据&#xff0c;参考AVFrame结…

jenkins 构建报错 Cannot run program “sh”

原因 在 windows 操作系统 jenkins 自动化部署的时候, 由于自动化构建的命令是 shell 执行的,而默认windows 从 path 路径拿到的 shell 没有 sh.exe &#xff0c;因此报错。 解决方法 前提是已经安装过 git WINR 输入cmd 打开命令行, 然后输入where git 获取 git 的路径, …

基于Spring Boot的高校物品捐赠管理系统设计与实现,LW+源码+讲解

摘 要 传统办法管理信息首先需要花费的时间比较多&#xff0c;其次数据出错率比较高&#xff0c;而且对错误的数据进行更改也比较困难&#xff0c;最后&#xff0c;检索数据费事费力。因此&#xff0c;在计算机上安装高校物品捐赠管理系统软件来发挥其高效地信息处理的作用&a…

AndroidStudio通过Bundle进行数据传递

作者&#xff1a;CSDN-PleaSure乐事 欢迎大家阅读我的博客 希望大家喜欢 使用环境&#xff1a;AndroidStudio 目录 1.新建活动 2.修改页面布局 代码&#xff1a; 效果&#xff1a; 3.新建类ResultActivity并继承AppCompatActivity 4.新建布局文件activity_result.xml 代…

测试分层:减少对全链路回归依赖的探索!

引言&#xff1a;测试分层与全链路回归的挑战 在软件开发和测试过程中&#xff0c;全链路回归测试往往是一个复杂且耗费资源的环节&#xff0c;尤其在系统庞大且模块众多的场景下&#xff0c;全链路测试的集成难度显著提高。而“测试分层”作为一种结构化的测试方法&#xff0…

【python】OpenCV—findContours(4.5)

文章目录 1、功能描述2、原理分析3、代码实现4、效果展示5、完整代码6、参考 1、功能描述 输入图片&#xff0c;计算出图片中的目标到相机间的距离 2、原理分析 用最简单的三角形相似性 已知参数&#xff0c;物体的宽度 W W W&#xff0c;物体到相机的距离 D D D&#xff0…

jmeter基础01-3_环境准备-Linux系统安装jdk

Step1. 查看系统类型 打开终端&#xff0c;命令行输入uname -a&#xff0c;显示所有系统信息&#xff0c;包括内核名称、主机名、内核版本等。 如果输出是x86_64&#xff0c;则系统为64位。如果输出是i686 或i386&#xff0c;则系统为32位。 Step2. 官网下载安装包 https://www…

获取JSON对象的时候,值会自动带上双引号

问题&#xff1a;当使用下方代码&#xff0c;获取JsonNode对象的时候&#xff0c;从该对象中通过键获取的值会自动带上双引号。 JsonNode jsonNode new ObjectMapper().readTree("JSON字符串"); 注意&#xff1a;以上方法是获得的JsonNode对象&#xff0c;不是JSO…

大气污染监测系统方案:智慧环保监测的“千里眼“

​ 作为星创易联的一名工程师,我有幸参与了某市环保局的大气污染监测系统项目。该市地处我国中部地区,近年来工业发展迅速,大气污染问题日益突出。为加强环境管理,政府决定构建一套覆盖全市的大气污染在线监测系统,实时掌握各区域的空气质量状况。 我们公司凭借在物联网领域的…

leetcode-88-合并两个有序数组

题解&#xff1a; 解法一&#xff1a;从后向前同时遍历两个数组&#xff0c;因为nums1后面是0&#xff0c;从后遍历节省空间。 1、定义三个指针&#xff0c;分别为&#xff1a;len1m-1指向nums1的最后一个非0数字&#xff1b;len2n-1指向nums2的最后一个数字&#xff1b;len3…

百度文心智能体:巧用汉字笔画生成与汉字搜索插件,打造一个学习汉字的教育类智能体

这篇文章&#xff0c;主要介绍如何巧用汉字笔画生成与汉字搜索插件&#xff0c;打造一个学习汉字的教育类智能体。 目录 一、教育类智能体 1.1、智能体演示 1.2、智能体插件 1.3、智能体prompt &#xff08;1&#xff09;角色和目标 &#xff08;2&#xff09;思考路径 …

MySQL rand()函数、rand(n)、生成不重复随机数

文章目录 一、rand()与rand(n)二、rand()使用示例2.1、rand()与order by/group by使用随机排序分组2.2、round()与rand()的组合使用2.3、rand与ceiling的组合使用2.4、rand与floor组合使用2.5、rand与md5组合使用 三、总结3.1、rand()与rand(n)的区别 有时候我们想要生成一个唯…

『Linux学习笔记』如何在 Ubuntu 22.04 上安装和配置 VNC

『Linux学习笔记』如何在 Ubuntu 22.04 上安装和配置 VNC 文章目录 一. 『Linux学习笔记』如何在 Ubuntu 22.04 上安装和配置 VNC1. 介绍 二. 参考文献 一. 『Linux学习笔记』如何在 Ubuntu 22.04 上安装和配置 VNC 如何在 Ubuntu 22.04 上安装和配置 VNChttps://hub.docker.c…

ubuntu22-安装vscode-配置shell命令环境-mac安装

文章目录 1.安装vscode2.修改语言为中文3.配置bash调试环境3.1.安装插件3.2.添加配置文件 4.调试bash4.1.新建tmp.sh文件4.2.运行启动 5.mac安装6.mac卸载 1.安装vscode 从官网下载安装包Code_1.93.1-1726079302_amd64.deb。 在ubuntu系统中&#xff0c;安装包所在目录打开命令…

MongoDB 8.0.3版本安装教程

MongoDB 8.0.3版本安装教程 一、下载安装 1.进入官网 2.选择社区版 3.点击下载 4.下载完成后点击安装 5.同意协议&#xff0c;下一步 6.选择第二个Custon&#xff0c;自定义安装 7.选择安装路径 &#xff01;记住安装路径 8.默认&#xff0c;下一步 9.取…

编程八种语言谁是最受市场青睐的?

你听说过"编程语言江湖"吗?在这个瞬息万变的IT世界里&#xff0c;各种编程语言就像武林高手&#xff0c;各展绝技&#xff0c;争夺"武林盟主"的宝座。 1. JavaScript/TypeScript: 江湖新贵的崛起江湖中有一句老话:"十年磨一剑&#xff0c;霜刃未曾试…