優(yōu)雅!Spring Boot處理日志中的敏感數(shù)據(jù)
環(huán)境:Spring Boot3.2.5
1. 簡(jiǎn)介
在當(dāng)前的數(shù)據(jù)保護(hù)時(shí)代,我們必須高度重視個(gè)人敏感數(shù)據(jù)的處理與記錄。鑒于數(shù)據(jù)量龐大,我們?cè)谟涗涍@些信息時(shí),必須格外小心,采取適當(dāng)措施來(lái)屏蔽或保護(hù)用戶(hù)的敏感信息,以保障他們的隱私權(quán)不受侵犯。
本篇文章,我將詳細(xì)介紹如何使用 Logback 隱藏日志中的敏感數(shù)據(jù)。雖然這種方法是日志文件的最后一道防線,但它并不是解決問(wèn)題的最終方案。敏感數(shù)據(jù)是指任何需要防止未經(jīng)授權(quán)訪問(wèn)的信息。這可能包括任何個(gè)人身份信息,如社會(huì)安全號(hào)碼、銀行信息、登錄憑據(jù)、地址、電子郵件等。
Logback 是 Java 社區(qū)使用最廣泛的日志框架之一。它取代了其前身 Log4j。Logback 的實(shí)現(xiàn)速度更快,配置選項(xiàng)更多,而且在歸檔舊日志文件方面更具靈活性。
接下來(lái),我們將在登錄應(yīng)用程序日志時(shí)屏蔽屬于用戶(hù)的敏感數(shù)據(jù)。
2. 實(shí)戰(zhàn)案例
2.1 環(huán)境準(zhǔn)備
準(zhǔn)備實(shí)體對(duì)象
public class User {
private Long id ;
private String name ;
private String address ;
private String phone ;
private String password ;
private String email ;
// getters, setters
}
我們后續(xù)將對(duì)上面的name,phone,password,email進(jìn)行脫敏處理。
接口定義
@GetMapping("/login")
public User login() throws Exception {
// TODO
User user = new User(1L, "張三", "新疆", "1399999999", "123456789", "xxxooo@qq.com") ;
logger.info("用戶(hù)信息: {}", new ObjectMapper().writeValueAsString(user)) ;
return user ;
}
在正常情況下訪問(wèn)上面的接口日志輸出如下:
圖片
接下來(lái),我們將要處理這里的敏感字段。
2.2 自定義PatternLayout
PatternLayout類(lèi)可以通過(guò)模式字符串配置的靈活布局。這個(gè)類(lèi)的目標(biāo)是格式化一個(gè)ILoggingEvent并以{#link String}的形式返回結(jié)果。結(jié)果的格式取決于轉(zhuǎn)換模式。
public class MaskingPatternLayout extends PatternLayout {
private Pattern multilinePattern;
private List<String> maskPatterns = new ArrayList<>();
public void addMaskPattern(String maskPattern) {
maskPatterns.add(maskPattern);
multilinePattern = Pattern.compile(maskPatterns.stream().collect(Collectors.joining("|")), Pattern.MULTILINE);
}
@Override
public String doLayout(ILoggingEvent event) {
return maskMessage(super.doLayout(event));
}
private String maskMessage(String content) {
if (multilinePattern == null) {
return content;
}
StringBuilder sb = new StringBuilder(content);
Matcher matcher = multilinePattern.matcher(sb);
while (matcher.find()) {
IntStream.rangeClosed(1, matcher.groupCount()).forEach(group -> {
if (matcher.group(group) != null) {
IntStream.range(matcher.start(group), matcher.end(group)).forEach(i -> sb.setCharAt(i, '*'));
}
});
}
return sb.toString();
}
}
PatternLayout.doLayout()的實(shí)現(xiàn)負(fù)責(zé)在應(yīng)用程序的每條日志信息中屏蔽匹配的數(shù)據(jù),前提是這些數(shù)據(jù)與配置的模式之一相匹配。
接下來(lái)就可以在logback-spring.xml中配置;logback.xml 中的 maskPatterns 列表可構(gòu)建多行模式。如果它以屬性列表的形式出現(xiàn),那么每個(gè)配置項(xiàng)都會(huì)調(diào)用 addMaskPattern。
2.3 配置Appender
<appender name="mask" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="com.pack.sensitive.log.MaskingPatternLayout">
<maskPattern>\"name\"\s*:\s*\"(.*?)\"</maskPattern>
<maskPattern>\"phone\"\s*:\s*\"(.*?)\"</maskPattern>
<maskPattern>\"password\"\s*:\s*\"(.*?)\"</maskPattern>
<maskPattern>([\w.-]+@[\w.-]+\.\w+)</maskPattern>
<pattern>%d{22:mm:ss} %-5level %logger Line:%-3L - %msg%n</pattern>
<charset>UTF-8</charset>
</layout>
</encoder>
</appender>
這里我添加了4個(gè)模式用來(lái)匹配上面提到的4個(gè)字段。有個(gè)該配置后再次進(jìn)行訪問(wèn)上面的接口
圖片
控制臺(tái)日志輸出
正確的將我們需要處理的字段進(jìn)行了脫敏。
如果我們是直接打印的對(duì)象呢?
User user = new User(1L, "張三", "新疆", "1399999999", "123456789", "xxxooo@qq.com") ;
logger.info("用戶(hù)信息: {}", new ObjectMapper().writeValueAsString(user)) ;
logger.info("直接打印對(duì)象: {}", user) ;
日志輸出
圖片
對(duì)于這種情況,我們還需要定義更多的模式,如下:
<maskPattern>name\s*=\s*(.*?),</maskPattern>
<maskPattern>password\s*=\s*(.*?),</maskPattern>
再次訪問(wèn)接口
正確的脫敏了此種情況。