环境搭建
搭建基础的spring boot工程,引入logback-spring.xml基础础配置文件
基本实现方式
修改配置
在logback-spring文件的基础上添加脱敏开关,启动监听器,及日志转换器
<!--是否脱敏配置-->
<if condition='"false".equals(property("SENSITIV_FLAG"))'>
<then>
<!-- 关闭日志脱敏 -->
<property scope="context" name="converterCanRun" value="false"/>
</then>
<else>
<!-- 开启日志脱敏 -->
<property scope="context" name="converterCanRun" value="true"/>
</else>
</if>
<!--启动监听器-->
<contextListener class="com.person.share.log.LoggerStartupListener"/>
<!--日志转换器-脱敏-->
<conversionRule conversionWord="msg" converterClass="com.person.share.log.SensitiveConverter"> </conversionRule>
实现描述
LoggerStartupListener的定义如下:
用于初始化加载需要进行敏感字段处理的ContextListener,根据context对应的property加载自定义的需要的字段
public class LoggerStartupListener extends ContextAwareBase implements LoggerContextListener, LifeCycle {
private boolean started = false;
public void start() {
if (!this.started) {
this.prepareSensitiveDataKeyList();//加载自定义的脱敏字段,如添加一个手机号的字段列表
this.started = true;
}
}
private void prepareSensitiveDataKeyList() {
String allowRun = this.context.getProperty("converterCanRun");
if ("true".equals(allowRun)) {
List<String> phoneList = SensitiveConverter.phoneList;
String extPhoneList = this.context.getProperty("SENSITIVE_PHONE");
if (StringUtils.isNotBlank(extPhoneList)) {
phoneList.addAll(Arrays.asList(extPhoneList.split(",")));
}
SensitiveConverter.phoneList = phoneList;
if (CollectionUtils.isNotEmpty(SensitiveConverter.sensitiveDataKeyList)) {
return;
}
SensitiveConverter.sensitiveDataKeyList.addAll(SensitiveConverter.ignoreList);
SensitiveConverter.sensitiveDataKeyList.addAll(SensitiveConverter.phoneList);
}
}
public boolean isStarted() {
return this.started;
}
}
SensitiveConverter的定义如下:
脱敏规则的实现,如下是对通过phoneNo记录手机号的json字符串进行md5处理的一种可能方式
public class SensitiveConverter extends MessageConverter {
private String converterCanRun;
public static List<String> ignoreList = Lists.newArrayList();
/**
* 包含电话号码的字段过滤
*/
public static List<String> phoneList = Arrays.asList("phoneNo","phone");
public static List<String> sensitiveDataKeyList = Lists.newArrayList();
@Override
public String convert(ILoggingEvent event) {
Context context = getContext();
String allowRun = context.getProperty("converterCanRun");
setConverterCanRun(StringUtils.defaultIfBlank(allowRun,"true"));//默认进行脱敏
String originMsg = event.getFormattedMessage();
originMsg = sensitiveInvoke(originMsg);
return originMsg;
}
private String sensitiveInvoke(String originMsg) {//originMsg的格式形如{"phoneNo":"12345678900"}
String tempMsg = originMsg;
if ("true".equals(this.converterCanRun)) {
try {
if (sensitiveDataKeyList.size() > 0) {
Iterator var3 = sensitiveDataKeyList.iterator();
while(var3.hasNext()) {
String key = (String)var3.next();
int index = -1;
while(true) {
index = tempMsg.indexOf(key, index + 1);
if (index != -1 ) {
int valueStart = this.getValueStartIndex(tempMsg, index + key.length());
int valueEnd = this.getValueEndEIndex(tempMsg, valueStart);
String subStr = tempMsg.substring(valueStart, valueEnd);
subStr = mask(subStr, key);
tempMsg = tempMsg.substring(0, valueStart) + subStr + tempMsg.substring(valueEnd);
}
if (index == -1) {
break;
}
}
}
}
} catch (Exception var9) {
}
}
return tempMsg;
}
private int getValueStartIndex(String msg, int valueStart) {
while(valueStart < msg.length()) {
char ch = msg.charAt(valueStart);
if (ch == ':') {
++valueStart;
ch = msg.charAt(valueStart);
if (ch == '"') {
++valueStart;
}
return valueStart;
}
++valueStart;
}
return valueStart;
}
private int getValueEndEIndex(String msg, int valueEnd) {
while(true) {
if (valueEnd != msg.length()) {
char ch = msg.charAt(valueEnd);
if (ch == '"') {
if (valueEnd + 1 != msg.length()) {
char nextCh = msg.charAt(valueEnd + 1);
if ( nextCh != ',' && nextCh !='}') {
++valueEnd;
continue;
}
while(valueEnd > 0) {
char preCh = msg.charAt(valueEnd - 1);
if (preCh != '\\') {
break;
}
--valueEnd;
}
}
} else if ( ch != ',' && ch != '}' ) {
++valueEnd;
continue;
}
}
return valueEnd;
}
}
private String mask(String subMsg, String key) {
if (ignoreList.contains(key)) {
return "*IGNORE*";
} else if (phoneList.contains(key)) {
return "'" + MD5Util.MD5(subMsg) + "'";
}else {
return subMsg;
}
}
public void setConverterCanRun(String allowRun){
this.converterCanRun = allowRun;
}
}
MD5Util是md5的一个工具类实现
实现效果
如上启动对应的应用后,当使用日志log.info or其他方式记录日志时,原文为{“phoneNo”:“12345678900”}在日志文件中可能被记录为*{“phoneNo”:“‘dhfidhgidae3hgd8dgd’”}*这样的md5的概要描述。如果需要在控制台上也要以密文输出的话,可以脱敏配置之后添加ConsoleAppender,类似下面的配置
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>
%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger - %X{url}?%X{queryString} code:%X{businessResult} - %msg%n
</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>