SpringBoot2.7還是任性的,就是不支援Logback1.3,你能奈他何

青石路發表於2024-07-29

開心一刻

今天上午,同事群中的劉總私聊我
劉總:你來公司多久了
我:一年了,劉總
劉總:你還年輕,機會還很多,年底了,公司要裁員
劉總語重心長的繼續說到:以後我們常聯絡,無論以後你遇到什麼困難,找我,我會盡量幫你!
我:所以了,我是被裁了嗎,呵,我爸知道嗎?
劉總:知道,今天上午保安部已經出名單了,你爸也在裡面
我:我媽知道嗎?
劉總:保潔也裁
我:劉總,你做這些事情問過我爺爺嗎?
劉總:門衛也裁
我心裡咯噔一下,這它媽噠團滅了呀

團滅

安全漏洞

公司的測試部門會定期掃描程式碼,檢測出安全漏洞,匯出 Excel放到群裡,各個專案的負責人針對性去修復(升級元件版本),因為某些原因不能修復的,需要給出原因(有些元件版本依賴更高的 JDK 版本,而 JDK 又不能升)。而我負責的專案是基於 Spring Boot 2.7.18,它依賴的 logback 版本是 1.2.12,存在安全漏洞 CVE-2023-6378

CVE-2023-6378

我本意是非常拒絕修這玩意的,修的時候得評估影響點,測的時候需要都覆蓋到;核心元件的升級不亞於一次重構,開發和測試都得全量測。重點是,修好不算產出,修壞了可是要背鍋的,我可是經歷過血的教訓的:都說了能不動就別動,非要去調整,出生產事故了吧,總之還是那句話

能不動就不要動,改好沒績效,改出問題要背鍋,吃力不討好,又不是不能跑

縱使我有萬般的不願,但也不得不修,公司對安全漏洞這一塊非常重視,畢竟要給客戶留下非常專業的形象。既然避無可避,那就坦然接受,充分評估影響點,做好全面的測試

幹就完了

漏洞修復

如何修復,想必大家都知道,剔除掉 spring-boot-starter-logging 依賴,引入新版本依賴,如下所示

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <artifactId>spring-boot-starter-logging</artifactId>
            <groupId>org.springframework.boot</groupId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
	<groupId>ch.qos.logback</groupId>
	<artifactId>logback-classic</artifactId>
	<version>${logback.version}</version>
</dependency>

升級到哪個版本,就值得仔細斟酌一番了。反正都要升級,那何不升級到最新版?安全漏洞少,甚至暫時沒漏洞。那也不是,因為 logback 依賴 JDK 版本,官方說明如下

logback與jdk關係

因為專案依賴的 JDK 版本是 8,所以我們將 logback 升級到 1.3 的最新版是最合適的;logback 1.3.x 依賴的 SLF4J 版本是 2.0.x,所以最終 pom.xml 調整成如下

<properties>
    <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <logback.version>1.3.14</logback.version>
    <slf4j.version>2.0.7</slf4j.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <exclusions>
            <exclusion>
                <artifactId>spring-boot-starter-logging</artifactId>
                <groupId>org.springframework.boot</groupId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>${logback.version}</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>${slf4j.version}</version>
    </dependency>
</dependencies>
logback1.3.14

貌似挺簡單的,對吧?編譯也不報錯,一切都很順利;一旦你執行,最煩人的 bug 就來了

Exception in thread "main" java.lang.NoClassDefFoundError: org/slf4j/impl/StaticLoggerBinder
	at org.springframework.boot.logging.logback.LogbackLoggingSystem.getLoggerContext(LogbackLoggingSystem.java:304)
	at org.springframework.boot.logging.logback.LogbackLoggingSystem.beforeInitialize(LogbackLoggingSystem.java:118)
	at org.springframework.boot.context.logging.LoggingApplicationListener.onApplicationStartingEvent(LoggingApplicationListener.java:238)
	at org.springframework.boot.context.logging.LoggingApplicationListener.onApplicationEvent(LoggingApplicationListener.java:220)
	at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:178)
	at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:171)
	at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:145)
	at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:133)
	at org.springframework.boot.context.event.EventPublishingRunListener.starting(EventPublishingRunListener.java:79)
	at org.springframework.boot.SpringApplicationRunListeners.lambda$starting$0(SpringApplicationRunListeners.java:56)
	at java.util.ArrayList.forEach(ArrayList.java:1249)
	at org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:120)
	at org.springframework.boot.SpringApplicationRunListeners.starting(SpringApplicationRunListeners.java:56)
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:299)
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1300)
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1289)
	at com.qsl.Application.main(Application.java:15)
Caused by: java.lang.ClassNotFoundException: org.slf4j.impl.StaticLoggerBinder
	at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	... 17 more

此時,你們會怎麼辦?本著快速解決 bug 的原則,我們也只能上網查問題

java.lang.ClassNotFoundException: org.slf4j.impl.StaticLoggerBinder

看能不能找到解決辦法;可一通查下來,各種嘗試,該問題都得不到解決,除非將 logback 版本降到 1.2.x,可最新的 1.2.13 是有不少安全漏洞的

logback1.2.13漏洞

那沒別的辦法了,只能去追查問題產生的原因了,找到原因就好對症下藥了。問題又來了,如何去查原因了,最直接、最有效的辦法就是從異常堆疊資訊入手

異常堆疊

滑鼠左擊 LogbackLoggingSystem.java:304,然後就來到 spring-boot-2.7.18 的原始碼

LogbackLoggingSystem_304

這裡用到了 StaticLoggerBinder,在往上滑到 LogbackLoggingSystemimport 部分,StaticLoggerBinder 的全類路徑是

org.slf4j.impl.StaticLoggerBinder

logback 1.2.12 是有這個類的

logback1.2.12_StaticLoggerBinder

logback 1.3.14 不僅沒有該類,連 org 包都不存在了

logback1.3.14_StaticLoggerBinder不存在

所以,原因是不是找到了?

spring-boot-2.7.18 依賴 org.slf4j.impl.StaticLoggerBinder,而 logback 1.3.14 沒有該類

那如何對症下藥了?不僅你們懵,我也懵

懵

調整下思路,這個問題我們肯定不是第一個遇到的,對吧,肯定有人在 spring-boot 的官方提問,我們去搜搜 org/slf4j/impl/StaticLoggerBinder

springboot2.7.x不支援logback1.3.x

點進去,裡面有官方人員給出的答覆,我給大家翻譯一下;提問者是 LSmyrnaios,他做了一下背景介紹

logback 1.3.x 基於 Java 8,1.4.x 基於 Java-11,而 Spring Boot 只在 3.x.x 中整合了 logback 1.4.x(基於 Java-11)

Java-8 使用者被遺忘了

根據 logback 文件說明,logback 同時維護 1.3.x 和 1.4.x,也就是說,logback 1.3.x 是 '活躍的',Spring Boot 2.7.x 應該整合它

請考慮以下案例:

我有一個Java-8應用程式,使用 logback v.1.3.6,執行沒問題

現在,我想將該應用程式整合到 Spring Boot v.2.7.9,執行的時候胞如下錯誤:

(異常堆疊跟我們遇到的一樣,不展示了)

看起來像是 Spring Boot 用的 slf4j 1.7.x,但是 logback 1.3.x 用的 slf4j 2.0.x,所以 StaticLoggerBinder 類不見了

所以,你們能夠在 Spring Boot >= 2.7.x and < 3 的版本中支援 logback 1.3.x 嗎

先謝謝了.

這麼看來,這哥們遇到的問題跟我們的一樣,提出的訴求也跟我們一樣,是不是看到了希望?

官方人員 scottfrederick 給出了回覆

scottfrederick回覆

翻譯過來就是

LSmyrnaios 感謝你的聯絡。 Spring Boot 2.7.x 依賴 Logback 1.2.x。 已經在第三方升級政策中說明過了,我們不會在 2.7.x 的版本中升級 Logback到 1.3.x。正如你提到的,我們不僅僅要升級 Logback 到 1.3.x,還需要將 SLF4J 升級到 2.0.x,這有一個關於我們為什麼不在 2.7.x 升級的討論,所以我們做補丁釋出

第三方依賴升級說明如下

Third-party dependencies

簡單點來說就是:第三方依賴的補丁級別的修復,可以在 Spring Boot 的補丁版本中升級,而第三方依賴的次要或者主要版本的升級,則只能在 Spring Boot 的次要或主要版本中升級。不能在 Spring Boot 的補丁版本中升級第三方依賴的次要或者主要版本。這裡的補丁版本可以理解成小版本,也就是 1.2.x 中的 x,而次要或者重要版本,則是 1.x.x 中的第一個 x,也就是我們所說的大版本

關於 scottfrederick,他可不是 Spring Boot 的普通 Contributor,人家可是榜六大哥

榜六

他說的還是很有權威的;關於他提到的討論,我們後面在看,先把當前的看完。提問者 LSmyrnaios 又說了

LSmyrnaios 補充

翻譯過來就是

scottfrederick,我們接著剛剛的討論,我想問的是,是否有可能在 Spring Boot 的下一個大版本(比如 2.8.0,如果在計劃中的話)將 SLF4J 升級到 2.0.x,logback 升級到 1.3.x

這對於大量的 Java 8 使用者來說非常重要,他們希望為生產系統提供最新的安全和錯誤修復

先謝謝了

scottfrederick 說的就很符合我們的期望,我們接著往下看。wilkinsona 給出了回覆

wilkinsona回覆

翻譯過來就是

目前沒有 Spring Boot 2.8 的計劃

言簡意賅,弦外之音就是 Spring Boot 2.x.x 就是不支援 Logback 1.3.x,滿滿的任性感。你們是不是很好奇這任性的哥們是誰?人家可是 Spring Boot 的榜一大哥!!!

榜一大哥

是不是覺得他任性的理所當然了?我們繼續往下看,ASarco 說了一句

ASarco

翻譯過來就是

現在的問題是 logback 1.2.12 存在安全漏洞 cve-2023-6378,對於 2.7.x 的煞筆使用者卻沒有一個結論

這哥們表達了自己得憤懣,都直接飆國粹(SB)了,那是相當的氣憤呀

SB 是 Spring Boot 的簡寫,並非國粹,大家別被誤導了!!!

針對 ASarco 的問題,後面有人給了回覆,可以升級 Logback1.2.13 來修復漏洞 cve-2023-6378,而我們也沒得選了,只能將 Logback1.3.14 降到 1.2.13,最終的 pom.xml 如下所示

<properties>
    <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	<logback.version>1.2.13</logback.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <exclusions>
            <exclusion>
                <artifactId>spring-boot-starter-logging</artifactId>
                <groupId>org.springframework.boot</groupId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>${logback.version}</version>
    </dependency>
</dependencies>

所以,1.2.13 的安全漏洞仍是存在的,下次掃描出來後我們直接說明如下

修復不了,Spring Boot 2.7.x 官方不打算支援 Logback 1.3.x,除非升級 Spring Boot 到 3.x.x(整合的是 Logback 1.4.x),但同時需要將 JDK 升級到 11

討論

還記得前面提到的那個討論嗎,因為比較長,我挑一些重點給大家翻譯下

1、wilkinsona 提到了 Logback 的一次 commit,這次提交移除了 StaticLoggerBinder

StaticLoggerBinder移除記錄

1.3.0-alpha0 版本就移除了 StaticLoggerBinder,所以 Spring Boot 2.7.x 不能整合 Logback 1.3.x 的任何一個版本

2、snicoll (榜二大哥) 提到了一個很重要的點

snicoll_討論

Spring BootLoggingSystem 是可以禁用或者改變的

-Dorg.springframework.boot.logging.LoggingSystem=none

3、wilkinsona 提到了 spring.factories 中的 ApplicationListener,其優先順序高於 org.springframework.boot.context.logging.LoggingApplicationListener,可以用來設定系統屬性以響應 ApplicationStartingEvent

4、wilkinsona 針對 cmuchinsky 提出的

對於 spring boot 2.7,是否有可能更新 ogbackLoggingSystemlogback 來相容 Logback 1.3 與 1.2,例如反射

給出了回答,他認為這不太可能,支援 Logback 1.4 所需的更改範圍太廣,無法透過反射並行支援 1.2 和 1.3/1.4

5、zhaolj214 透過讀原始碼,找到了一種解決方案

@SpringBootApplication
public class Spring5Application {
    public static void main(String[] args) {
        System.setProperty("org.springframework.boot.logging.LoggingSystem", "none");
        SpringApplication.run(Spring5Application.class, args);
    }
}

至於正確與否,我們下篇再試

6、wilkinsona 說明了可以自定義日誌:howto.logging

總結下來就是:針對 Spring Boot 2.7.x,官方不會支援 Logback 1.3.x,但還是可以透過自定義的方式去支援 Logback 1.3.x,具體如何自定義,以及效果如何,且聽下回分解

總結

  1. Logback 1.3 依賴 JDK 8,1.4 依賴 JDK 11;Spring Boot 2.7.x 依賴 Logback 1.2.x,而 3.x.x 依賴 Logback 1.4.x。也就說 Spring Boot 跳過了 Logback 1.3.x
  2. Spring Boot 官方也給出了答覆,根據第三方依賴政策(小版本升級小版本,大版本升級大版本),2.7.x 不會支援 Logback 1.3.x,而 3.x.x 索性直接支援 Logback 1.4.x
  3. 非要 Spring Boot 2.7.x 支援 Logback 1.3.x 也不是不可以,需要調整配置,還存在一些限制,具體細節請看下篇

相關文章