logback 日志脱敏 隐藏PII信息

PII信息全称:Personally identifiable information (PII)

PII is any information about an individual maintained by an agency, including

  • any information that can be used to distinguish or trace an individual‘s identity, such as name, social security number, date and place of birth, mother‘s maiden name, or biometric records;

  • any other information that is linked or linkable to an individual, such as medical, educational, financial, and employment information.

Link:(https://piwik.pro/blog/what-is-pii-personal-data/)

大概翻译一下就是能够代表一个人身份的一些信息,包括但不限于姓名、社会安全号码、出生日期和地点、母亲的婚前姓氏或生物识别记录。

为了提高网络安全集成评估,需要在日志中屏蔽这些信息。


以下是具体实现:

利用logback的Converter,自定义日志格式转换符,然后继承ClassicConverter

首先定义类:SensitiveDataConverter 继承父类:MessageConverter

具体代码:

package eu.digient.sdk.util;

import ch.qos.logback.classic.pattern.MessageConverter;
import ch.qos.logback.classic.spi.ILoggingEvent;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;


public class SensitiveDataConverter extends MessageConverter {
    private final static String SENSITIVE_DEFAULT_DATA_KEYS = "fullName,address,ipAddress,nationalIdNumber,email,phoneNumber,birthDate,country,city,bankCard";
    private final static String SENSITIVE_DATA_MASKING_ENABLED = "true";

    @Override
    public String convert(ILoggingEvent event) {
        String originLogMsg = event.getFormattedMessage();
        return filterSensitiveInformation(originLogMsg);
    }

    public String filterSensitiveInformation(final String oriMsg) {
        String sensitiveDataMaskingEnabled = StringUtil.isEmptyString(System.getenv("SENSITIVE_DATA_MASKING_ENABLED")) ? SENSITIVE_DATA_MASKING_ENABLED : System.getenv("SENSITIVE_DATA_MASKING_ENABLED");
        String sensitiveDataKeys           = StringUtil.isEmptyString(System.getenv("SENSITIVE_DATA_KEYS")) ? SENSITIVE_DEFAULT_DATA_KEYS : System.getenv("SENSITIVE_DATA_KEYS");
        String tempMsg                     = oriMsg;

        if ("true".equals(sensitiveDataMaskingEnabled)) {
            if (sensitiveDataKeys != null && sensitiveDataKeys.length() > 0) {
                String[] keysArray = sensitiveDataKeys.split(",");

                for (String key : keysArray) {
                    int index = -1;
                    do {
                        index = tempMsg.indexOf(key, index + 1);
                        if (index != -1 &&
                            !Pattern.matches("[a-zA-Z0-9]", tempMsg.substring(index + key.length(), index + key.length() + 1)) &&
                            (index == 0 || !Pattern.matches("[a-zA-Z0-9]", tempMsg.substring(index - 1, index)))
                        ) {

                            int valueStart = getValueStartIndex(tempMsg, index + key.length());
                            int valueEnd   = getValueEndIndex(tempMsg, valueStart);

                            String subStr = tempMsg.substring(valueStart, valueEnd);
                            subStr = mask(subStr);
                            tempMsg = tempMsg.substring(0, valueStart) + subStr + tempMsg.substring(valueEnd);
                        }
                    } while (index != -1);
                }
            }
        }
        return tempMsg;
    }
    
    /**
     * Get the index of the first character of sensitive information
     */
    private int getValueStartIndex(String msg, int valueStart) {
        while (true) {
            if (valueStart == msg.length()) {
                break;
            }
            char ch = msg.charAt(valueStart);
            if (ch == ':' || ch == '=') {
                valueStart++;
                ch = msg.charAt(valueStart);
                if (ch == '"') {
                    valueStart++;
                }
                break;
            } else {
                valueStart++;
            }
        }
        return valueStart;
    }

    /**
     * Get the index of the last character of sensitive information
     */
    private int getValueEndIndex(String msg, int valueEnd) {
        while (true) {
            if (valueEnd == msg.length()) {
                break;
            }
            char ch = msg.charAt(valueEnd);
            if (ch == '"') {
                if (valueEnd + 1 == msg.length()) {
                    break;
                }
                char nextCh = msg.charAt(valueEnd + 1);
                if (nextCh == ';' || nextCh == ',') {
                    while (valueEnd > 0) {
                        char preCh = msg.charAt(valueEnd - 1);
                        if (preCh != '\\') {
                            break;
                        }
                        valueEnd--;
                    }
                    break;
                } else {
                    valueEnd++;
                }
            } else if (ch == ';' || ch == ',' || ch == '}') {
                break;
            } else {
                valueEnd++;
            }
        }
        return valueEnd;
    }

    /**
     * Mask sensitive information
     */
    private String mask(String submsg) {
        if (StringUtils.isBlank(submsg)) {
            return "";
        }
        return StringUtils.rightPad(StringUtils.left(submsg, 0), StringUtils.length(submsg), "*");
    }
}

在logback.xml 文件中加入(一定要加在appender标签的前面,最好就加在configuration标签的第一行,不然不会正确加载)

    <conversionRule conversionWord="msg" converterClass="eu.digient.sdk.util.SensitiveDataConverter"/>

converterClass写上你方法的路径

最后把几个test也放在这里:

package eu.digient.sdk.util

import org.junit.Test
import static org.junit.Assert.assertEquals

class SensitiveDataConverterTest {
    SensitiveDataConverter sensitiveDataConverter = new SensitiveDataConverter()

    @Test
    void sensitiveDataTest() {
        assertEquals 'fullName =*******', sensitiveDataConverter.filterSensitiveInformation('fullName = tester')
        assertEquals 'fullName :*******', sensitiveDataConverter.filterSensitiveInformation('fullName : tester')
        assertEquals 'fullName=******,address=********', sensitiveDataConverter.filterSensitiveInformation('fullName=tester,address=some-add')
        assertEquals 'fullName:******,address:********', sensitiveDataConverter.filterSensitiveInformation('fullName:tester,address:some-add')
    }

    @Test
    void sensitiveDataConverter_withNomal() {
        def originLogMsg = '{nationalIdNumber=f88898b2677e62f1ad54b9e330c0a27e, currency=EUR, email=some@cn.com, fullName=%E5%BE%90%E5%BD%A6%E5%A8%9C, birthDate=2021-08-31}'
        def result = '{nationalIdNumber=********************************, currency=EUR, email=***********, fullName=***************************, birthDate=**********}'

        assertEquals result, sensitiveDataConverter.filterSensitiveInformation(originLogMsg)
    }

    @Test
    void sensitiveDataConverter_withJsonFormat() {
        def originLogMsg = '{"reason":"success ","result":{"jobid":"JH2131171027170837443588J6","fullName":"Tom","bankCard":"6226430106137525","nationalIdNumber":"130333198901192762","phoneNumber":"13210141605"}'
        def result = '{"reason":"success ","result":{"jobid":"JH2131171027170837443588J6","fullName":"***","bankCard":"****************","nationalIdNumber":"******************","phoneNumber":"************}'

        assertEquals result, sensitiveDataConverter.filterSensitiveInformation(originLogMsg)
    }

    @Test
    void sensitiveDataConverter_withJsonFormat2() {
        def originLogMsg = '{"body":"{\\"fullName\\":Tom,\\"address\\":\\someAdd\\}"}'
        def result = '{"body":"{\\"fullName\\":***,\\"address\\":*********}"}'

        assertEquals result, sensitiveDataConverter.filterSensitiveInformation(originLogMsg)
    }

    @Test
    void sensitiveDataConverter_withSemicolon() {
        def originLogMsg = '{nationalIdNumber=f88898b2677e62f1ad54b9e330c0a27el; currency=EUR; email=some@cn.com; fullName=%E5%BE%90%E5%BD%A6%E5%A8%9C; birthDate=2021-08-31}'
        def result = '{nationalIdNumber=*********************************; currency=EUR; email=***********; fullName=***************************; birthDate=**********}'

        assertEquals result, sensitiveDataConverter.filterSensitiveInformation(originLogMsg)
    }

    @Test
    void sensitiveDataConverter_withMoreChar() {
        def originLogMsg = '{nationalIdNumberOne=f88898b2677e62f1ad54b9e330c0a27el; currency=EUR; emailId=some@cn.com; somefullName=%E5%BE%90%E5%BD%A6%E5%A8%9C; birthDate=2021-08-31}'
        def result = '{nationalIdNumberOne=f88898b2677e62f1ad54b9e330c0a27el; currency=EUR; emailId=some@cn.com; somefullName=%E5%BE%90%E5%BD%A6%E5%A8%9C; birthDate=**********}'

        assertEquals result, sensitiveDataConverter.filterSensitiveInformation(originLogMsg)
    }
}

  • 6
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值