jasypt 3.0.4 Bug 分析與解決

背景

目前項目中使用 jasypt 來做配置項的加解密,但是在實際使用中發(fā)現(xiàn) 3.0.4 版本中 ,在配置中心動態(tài)刷新后,@ConfigurationProperties 的屬性全部變成加密數(shù)據(jù)(如 ENC(XXX=)

接下來是源碼和部分機制原理分析,如果關(guān)心解決方法的話,可以快進到 “解決思路” 章節(jié)

jasypt 原理分析

Spring 框架中,所有的配置數(shù)據(jù)都存儲在 PropertySource 中,我們經(jīng)常在 Spring 框架中使用 Environment 來讀取配置數(shù)據(jù)。Environment 內(nèi)部包含多個 PropertySource(例如 bootstrap.yml 和 application.yml 會被解析為兩個獨立的 PropertySource)。當調(diào)用 Environment.getProperty 時,其內(nèi)部會遍歷所有的 PropertySource,最終通過 PropertySource.getProperty 來尋找指定配置。

所以 jasypt 的核心思路就是代理(或者說包裝)所有的 PropertySource

jasypt 使用 EnableEncryptablePropertiesBeanFactoryPostProcessor 在Bean 初始化之前代理所有 PropertySource。代理后的類我們用 EncryptablePropertySource 來同一稱呼。

EnableEncryptablePropertiesBeanFactoryPostProcessor

EncryptablePropertySource.getProperty 實現(xiàn)邏輯大致如下:當讀取到配置后,如果 value 的格式是 ENC(xxx),將會調(diào)用解密方法對加密數(shù)據(jù)進行解密

用偽代碼和圖總結(jié)下上面的文字

小結(jié)

Spring Cloud 動態(tài)刷新配置機制分析

Spring Cloud 為標記 @ConfigurationProperties 的類提供了動態(tài)刷新的功能。文檔:https://cloud.spring.io/spring-cloud-static/spring-cloud.html#_environment_changes

這里我的配置中心是 Nacos(任何配置中心都可以通過發(fā)布事件或者調(diào)用 Spring Cloud endpoint 的方式來刷新配置)

當 Nacos 監(jiān)聽到配置文件更新時,會發(fā)布 RefreshEvent。發(fā)布事件后,代碼會執(zhí)行到 ContextRefresher,該類主要作用是通過創(chuàng)建一個新的 Spring Context 初始化(為了加載新的配置),然后發(fā)送 EnvironmentChangeEvent

ContextRefresher

當執(zhí)行完 93 行時,最新的配置已經(jīng)加載完畢。其實創(chuàng)建新的 Spring Context 也會執(zhí)行到 EnableEncryptablePropertiesBeanFactoryPostProcessor 邏輯,但是由于加載順序的原因,生成代理類之前并沒有加載 Nacos 的配置。

但是 jasypt 提供了一個 RefreshScopeRefreshedEventListener,該類監(jiān)聽了 RefreshScopeRefreshedEvent, EnvironmentChangeEvent, ServletWebServerInitializedEvent 提供了和 EnableEncryptablePropertiesBeanFactoryPostProcessor 類似的邏輯,將 PropertySource 轉(zhuǎn)換(代理)為 EncryptablePropertySource

RefreshScopeRefreshedEventListener

問題發(fā)現(xiàn)

但是此時發(fā)現(xiàn) PropertySource 可以被成功代理,但是配置密文卻無法解密,一番 debug 之后發(fā)現(xiàn),執(zhí)行 @ConfigurationProperties 綁定的類 ConfigurationPropertiesRebinder 先于 RefreshScopeRefreshedEventListener 執(zhí)行。

所以現(xiàn)在此時的問題就是如何調(diào)整 RefreshScopeRefreshedEventListener 的執(zhí)行順序。

這時我發(fā)現(xiàn)了 RefreshScopeRefreshedEventListener 使用了 @Order 注解并且實現(xiàn)了 Ordered 接口

RefreshScopeRefreshedEventListener @Order
RefreshScopeRefreshedEventListener getOrder

但是發(fā)現(xiàn)兩個順序并不一致(應(yīng)該是作者的疏忽),并且在實際代碼運行中,注解并不生效,這里我嘗試將 getOrder 順序修改為與直接一致,然后重新調(diào)試,發(fā)現(xiàn)問題已經(jīng)解決。

解決方案

  1. 調(diào)整 RefreshScopeRefreshedEventListener 順序,保證該類執(zhí)行順序先于 ConfigurationPropertiesRebinder

    可以選擇將順序調(diào)為 HIGHEST_PRECEDENCE,也可以選擇僅優(yōu)先于 RefreshScopeRefreshedEventListener ,不影響其他 Listener

  2. 參考 EnvironmentDecryptApplicationInitializer,使用 ApplicationContextInitializer 實現(xiàn)對 PropertySource 的代理。

    在編寫本文時,發(fā)現(xiàn) Spring Cloud 提供了類似 jasypt 的加解密方案,通過 debug 發(fā)現(xiàn) EnvironmentDecryptApplicationInitializer 的執(zhí)行時機要比 jasypt 更合適(不需要像 jasypt 一樣寫兩個類處理),更多細節(jié)讀者自行閱讀 EnvironmentDecryptApplicationInitializer 源碼

@Value 動態(tài)更新

@Value 方式動態(tài)更新并不會受到影響,因為本質(zhì)上 @Value 和 @ConfigurationProperties 的刷新機制不同


更新于 2023 年 2 月 1 日,我的 PR 已經(jīng)被作者 merge,用戶升級到 3.0.5 即可
https://github.com/ulisesbocchio/jasypt-spring-boot/pull/344

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容