[日志] 日志脱敏实践
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 定义对象脱敏Filter : TargetClassDesensitizationFilter
fastjson2 : 2.0.52
import com.alibaba.fastjson2.filter.NameFilter;
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 TargetClassDesensitizationFilter implements ValueFilter ,NameFilter {
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;
}
/**
* 修改 字段名称
* @param source
* @param fieldName
* @param fieldValue
* @return
*/
@SneakyThrows
@Override
public String process(Object source, String fieldName, Object fieldValue) {
if (fieldName == null || fieldName.length() == 0) {
return fieldName;
}
char[] nameChars = fieldName.toCharArray();
//nameChars[0] = Character.toUpperCase(nameChars[0]);//字段名称的首字母大写化 | 如: "startTime" => "StartTime"
//获取指定名称的 Field,并通过 getType 获取 Field 的 Class
Field field = source.getClass().getDeclaredField(fieldName);
if(isEnum( field.getType() ) && (fieldValue == null) ) {//枚举类型字段的字段值为 null 时
logger.debug("fieldName:{}, fieldValue:{}", fieldName, fieldValue);
}
return new String(nameChars);
}
/**
* 是否为枚举类
* @param clazz
* @return
*/
public static boolean isEnum(Class<?> clazz) {
return clazz != null && clazz.isEnum();
}
/**
* 文本脱敏 (脱敏策略应用于文本) | 基于 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#
}
}
X 参考文献
@SensitiveInfo注解 / SensitiveInfoUtils
ValueFilter :修改 Value / NameFilter :修改key / PropertyFilter :根据PropertyName和PropertyValue来判断是否序列化
BeforeFilter :序列化时在最前面添加内容 / AfterFilter :序列化时在最后面添加内容 /
PropertyPreFilter :根据PropertyName判断是否序列化
本文作者:
千千寰宇
本文链接: https://www.cnblogs.com/johnnyzen
关于博文:评论和私信会在第一时间回复,或直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
日常交流:大数据与软件开发-QQ交流群: 774386015 【入群二维码】参见左下角。您的支持、鼓励是博主技术写作的重要动力!
本文链接: https://www.cnblogs.com/johnnyzen
关于博文:评论和私信会在第一时间回复,或直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
日常交流:大数据与软件开发-QQ交流群: 774386015 【入群二维码】参见左下角。您的支持、鼓励是博主技术写作的重要动力!

浙公网安备 33010602011771号