1 序言

思路:根据待输出的关键字段名称进行不可逆算法的(离线式)脱敏。

2 步骤

2.1 修改本工程的日志框架为 Log4j2

slf4j.version = 1.7.25
log4j.version = 2.13.3

<!-- log [start] -->
<dependency>
	<groupId>org.slf4j</groupId>
	<artifactId>slf4j-api</artifactId>
	<version>${slf4j.version}</version>
</dependency>
<dependency>
	<groupId>org.apache.logging.log4j</groupId>
	<artifactId>log4j-api</artifactId>
	<version>${log4j.version}</version>
</dependency>
<dependency>
	<groupId>org.apache.logging.log4j</groupId>
	<artifactId>log4j-core</artifactId>
	<version>${log4j.version}</version>
</dependency>
<dependency>
	<groupId>org.apache.logging.log4j</groupId>
	<artifactId>log4j-slf4j-impl</artifactId>
	<version>${log4j.version}</version>
</dependency>
<!-- log [end] -->

2.2 基于Log4j2自定义 StringLayout

Log4j2Rule

import com.google.common.collect.Lists;
import java.util.List;

public class Log4j2Rule {
    public static List<String> regulars = Lists.newArrayList();
    public static final String KEY_REGEXP = "@#.*?#@";

    public Log4j2Rule() {
    }

    static {
        regulars.add("@#.*?#@");
    }
}

CustomPatternLayout

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.johnnyzen.Log4j2Rule;
import java.nio.charset.Charset;
import java.util.Iterator;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
import org.apache.logging.log4j.core.layout.AbstractStringLayout;
import org.apache.logging.log4j.core.layout.PatternLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Plugin(
    name = "CustomPatternLayout",
    category = "Core",
    elementType = "layout",
    printObject = true
)
public class CustomPatternLayout extends AbstractStringLayout {
    public static final Logger logger = LoggerFactory.getLogger(CustomPatternLayout.class);
    private PatternLayout patternLayout;

    protected CustomPatternLayout(Charset charset, String pattern) {
        super(charset);
        this.patternLayout = PatternLayout.newBuilder().withPattern(pattern).build();
    }

    public String hideMarkLog(String logStr) {
        try {
            if (!StringUtils.isBlank(logStr) && !CollUtil.isEmpty(Log4j2Rule.regulars)) {
                Iterator var2 = Log4j2Rule.regulars.iterator();

                while(var2.hasNext()) {
                    String regular = (String)var2.next();
                    if (ReUtil.count(regular, logStr) > 0) {
                        logStr = matchingAndEncrypt(logStr, regular);
                    }
                }

                return logStr;
            } else {
                return logStr;
            }
        } catch (Exception var4) {
            logger.info(">>>>>>>>> 脱敏处理异常 ERROR:{}", var4);
            return logStr;
        }
    }

    private static String matchingAndEncrypt(String msg, String regExp) {
        return ReUtil.replaceAll(msg, regExp, (matcher) -> {
            String group = matcher.group();
            if (org.apache.commons.lang.StringUtils.isNotEmpty(group)) {
                group = StrUtil.sub(group, 2, -2);
                return SecureUtil.sha256(group);
            } else {
                return msg;
            }
        });
    }

    @PluginFactory
    public static Layout createLayout(@PluginAttribute("pattern") String pattern, @PluginAttribute("charset") Charset charset) {
        return new CustomPatternLayout(charset, pattern);
    }

    public String toSerializable(LogEvent event) {
        return this.hideMarkLog(this.patternLayout.toSerializable(event));
    }
}

2.3 修改日志的配置策略 (log4j.properties)

appender.{accessRollingFile/...}.layout.type=CustomPatternLayout

2.4 定义对象脱敏dValueFilter : TargetClassDesensitizationValueFilter


import com.alibaba.fastjson2.filter.ValueFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Base64Utils;
import org.springframework.util.ObjectUtils;

import java.util.ArrayList;
import java.util.List;

public class TargetClassDesensitizationValueFilter implements ValueFilter {
    private final static Logger logger = LoggerFactory.getLogger(VehicleDesensitizationValueFilter.class);

    /**
     * 脱敏规则
     */
    public final static String DESENSITIZATION_REGEXP = "@#.*?#@";

    /** 用于 String.format(DESENSITIZATION_TEMPLATE, targetField) 的脱敏格式模板 **/
    public final static String DESENSITIZATION_TEMPLATE = "@#%s#@";

    public static String TARGET_FIELD_PARAM = "idCard";

    public static String TARGET_FIELD_LIST_PARAM = "idCardList";

    /**
     * 对象脱敏 | 基于 上述自定义的 Layout : @#%s#@
     * @param object
     * @param propertyName
     * @param propertyValue
     * @return
     */
    @Override
    public Object apply(Object object, String propertyName, Object propertyValue) {
        logger.debug("object: {}, propertyName:{}, propertyValue: {}", object, propertyName, propertyValue);
        Object newPropertyValue = null; // 只要字段名中包含 TARGET_FIELD_PARAM ,则:值输出为 returnValue;
        if (propertyName.equals(TARGET_FIELD_PARAM)) {
            if(propertyValue instanceof String){
                String targetFieldValue = (String) propertyValue;
                return String.format(DESENSITIZATION_TEMPLATE, targetFieldValue);//  返回修改后的属性值
            } else {
                //Do Nothing
            }
        } else if(propertyName.equals(TARGET_FIELD_LIST_PARAM)) {
            if( (!ObjectUtils.isEmpty(propertyValue)) && (propertyValue instanceof List) ){
                List list = (List) propertyValue;
                Object firstElement = list.get(0);
                if(firstElement instanceof String){// list's data structure is List<String>
                    List<String> newTargetFieldValueList = new ArrayList<>();
                    targetFieldValueList.stream().forEach( targetFieldValue -> {
                        newTargetFieldValueList.add(String.format(DESENSITIZATION_TEMPLATE, targetFieldValue));
                    });
                    newPropertyValue = newTargetFieldValueList;
                    return newPropertyValue;
                }  else {
                   //Do Nothing
                }
            } else {
                //Do Nothing
            }
        }
        return propertyValue;
    }

    /**
     * 文本脱敏 (脱敏策略应用于文本) | 基于 base64
     * @return
     */
    public static String textDesensitization(String content){
        if(ObjectUtils.isEmpty(content)){
            return null;
        } else {
            if(content.contains(TARGET_FIELD_PARAM) || content.contains(TARGET_FIELD_LIST_PARAM)){
                return "#DESENSITIZATION_START#" + Base64Utils.encodeToString(content.getBytes()) + "#DESENSITIZATION_END#";
            }
        }
        return content;
    }
}

2.5 修改明文格式输出日志的代码为脱敏输出格式

	将含 targetFiled 的日志明文 转密文
		sample:  “3343243536” => "@#3343243536#@"
		
		场景1: 打印基本数据类型、字符串
			logger.info("targetFiled: {}", String.format("@#%s#@", "3343243536"));
	
		场景2: 打印对象
		    {"vin": "436345345"}
			logger.info("targetObject: {}", JSON.toJSONString(pageRequest, new TargetClassDesensitizationValueFilter()) );

2.6 打印效果

public class LogDesensitizationTest {
    private final static Logger logger = LoggerFactory.getLogger(LogDesensitizationTest.class);

    @Test
    public void test(){
        Map<String, Object> params = new HashMap<>();
        params.put("idCard", "35355");//{"idCard":"c69c21ce30afa92d6df9606c906fc1a4982922ae64982e20018ada97a3b1174a"}

        logger.info("idCard: {}", com.alibaba.fastjson2.JSON.toJSONString(params, new TargetClassDesensitizationValueFilter()));//
        logger.info("idCard: {}", TargetClassDesensitizationValueFilter.textDesensitization(String.format("idCard:43534534")));//#DESENSITIZATION_START#aWRDYXJkOjQzNTM0NTM0#DESENSITIZATION_END#
    }
}