1、前言
目前的Java項目中,Spring boot框架已經成為不二選擇,無論是傳統分布式系統還是基於Spring cloud的微服務系統,都需要Spring boot作為底層開發框架。系統的開發,大量定制化的配置信息都集中在類似application.yml這樣的配置文件中,其中就包括數據庫鏈接口令等敏感信息。由於系統安裝到服務器后,配置文件對所有可訪問用戶都是開放的,如果敏感信息明文寫在配置文件中,泄露風險很高,必須對配置文件中的敏感信息進行加密。
2、加密思路
對配置信息進行加密的基本思路是:
(1)將密文寫入到配置文件中,並提供加密標識(可以不提供,通過配置信息的關鍵字區分,例如讀取到password的屬性值后,默認該值是加密的)
(2)指定密鑰的存儲位置
(3)在使用配置信息前,獲取密鑰,並對密文進行解密,將解密后的值賦予對應配置項。
3、屬性值的加解密過程
配置文件中屬性分為兩類,一種是自定義屬性,是開發過程中根據需要寫入到配置文件中;另一種是非自定義屬性,是框架或其他組件約定的屬性,像"jdbc.url"、"jdbc.password"這樣的。
3.1 自定義屬性加解密過程
自定義屬性需要手動加載進JavaBean后使用,對配置信息的解密過程可以在加載過程中進行。
基本步驟是:
(1) 使用加密工具加密機密信息,將密文寫入到配置文件中;
(2)實現配置信息加載邏輯 ,在加載過程中調用解密工具,將密文譯成明文;
獲取密鑰的過程參見4.4小節.
下面是配置信息加載邏輯的代碼示例。
3.1.1 配置文件內容(YAML文件)
testProps:
# 'xxxxx'為密文
testPassword: xxxxxx
3.1.2 配置信息加載邏輯
package com.test;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component //將TestProps類注冊到容器中
@ConfigurationProperties(prefix = "testProps")
public class TestProps {
private String testPassword;
public void setTestPassword(String testPassword) {
// 使用解密工具解密,密鑰通過解密工具獲取,加解密實現細節不是本文重點,不再展開介紹
String decryptPwd = CustomPropsDecryptUtil.decrypt(testPassword)
this.testPassword = decryptPwd;
}
}
3.2 非自定義屬性的加解密過程
非自定義屬性的加載過程由Spring boot框架自動進行,對這些屬性值的解密操作可以在加載配置文件時進行,示例代碼如下:
3.2.1 配置文件內容(YAML文件)
spring:
# spring datasource
datasource:
#賬號配置
url: jdbc:mysql://127.0.0.1:3306/dbName?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
username: userXX
password: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
driver-class-name: com.mysql.cj.jdbc.Driver
3.2.2 在配置文件加載過程中解密
import java.util.Properties;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
/**
* 配置文件解密核心類
*/
public class CustomPropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer {
protected void processProperties(ConfigurableListableBeanFactory beanFactory, Properties props)
throws BeansException {
try{
String password = props.getProperty("spring.datasource.password");
if (password != null) {
props.setProperty("spring.datasource.password",
CustomPropsDecryUtil.decrypt(password));
}
super.processProperties(beanFactory, props);
} catch (Exception e) {
e.printStackTrace();
throw new BeanInitializationException(e.getMessage());
}
}
}
4、JASYPT加密工具
系統單獨實現配置信息的加解密過程,有很多好處,比如加解密工具是自己實現的,可以根據需要調整加解密算法。但是也有很多缺點,比較突出的就是加解密實現比較繁瑣且具有定制化特征,復用麻煩。如果不是對加密過程有特殊要求,可以使用現有的一些開源加密工具,像比較流行jasypt-spring-boot-starter。
4.1 引入依賴
項目引入工具后,完成基本配置后,框架會自動完成對配置信息的解密過程,省去了手動解密的工作。下面是依賴引入代碼 :
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
JASYPT工具提供了非常豐富定制功能,包括自定義加\解密器、自定義加密標識的前綴和后綴等功能,本文對這部分內容不展開介紹,需要的可以參考JASYPT官方文檔。
4.2 對屬性進行加密
4.2.1 命令行加密(推薦)
在項目中引入jasypt工具后,本地maven倉庫會生成對應的工具jar包,可以調用jar包對屬相值進行加密,示例命令如下:
java -cp jasypt-1.9.2.jar org.jasypt.intf.cli.JasyptPBEStringEncryptionCLI password=1Qaz0oKm algorithm=PBEWithMD5AndDES input=root
輸出結果如下:
----ENVIRONMENT-----------------
Runtime: Oracle Corporation Java HotSpot(TM) 64-Bit Server VM 25.171-b11
----ARGUMENTS-------------------
input: root
algorithm: PBEWithMD5AndDES
password: 1Qaz0oKm
----OUTPUT----------------------
NZmLHOOHX0SEjc285iG9YQ==
4.2.2 工具類加密
除了使用命令行加密外,也可以在項目中實現加密工具,調用JASYPT工具進行加密,不推薦使用此方法,因為需要將加密使用的鹽值明文寫入到代碼中,如果鹽值泄露,對方就能使用JASYPT工具解密出加密配置信息。如果使用,強烈建議在項目打包時,排除掉工具類。工具類示例代碼,網上有很多,這里不再展示。
4.3 密文寫入到配置文件
將密文寫入到配置文件,配置時需要指定加密標識,方便框架識別出是否需要解密。配置示例如下 :
spring:
# spring datasource
datasource:
url: jdbc:mysql://127.0.0.1:3306/dbName?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
username: userXX
# 'ENC('為默認加密標識前綴 ,')'為默認加密標識后綴
password: ENC(xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx)
driver-class-name: com.mysql.cj.jdbc.Driver
4.4 指定JASYPT工具獲取SALT方法
JASYPT工具獲取SALT的方式有很多種,常見的有以下幾種:
(1)系統配置文件中獲取。
網上很多文章都是將salt放在系統配置文件中,這種方式非常危險,讓配置信息的加密形同虛設,不推薦使用這種方式。
(2)系統啟動參數指定
在系統啟動時,通過啟動命令,指定salt的值,例如:java -Djasypt.encryptor.password=e9fbdb2d3b21 -jar xxxDemo.jar
。這種方式比較安全,而且系統只在系統啟動時提供一次,泄露的風險比較低,推薦使用。
(3)服務器的環境變量里配置
可以在系統安裝服務器中,配置環境變量,指定salt的值。這種方式存在風險。
(4)外置文件存儲salt值
外置文件存儲salt值得思路是,salt值存儲在系統外的其他位置中,修改系統啟動邏輯,在系統啟動時,從外置文件中獲取slat值,這種方式比較安全,但是操作繁瑣,實現代價較高。
5. 總結
配置文件中的信息加密,對業務功能沒有幫助,而且會增加研發工作量,在部分系統中,登錄口令等敏感信息都是明文寫在配置文件中的。從系統安全性考慮,這樣的做法是比較嚴重的錯誤,一旦安裝服務器登錄口令被破解,系統就完全暴露給入侵者。本文介紹了在Spring boot框架下,對配置文件加密的實現方法,可以為研發實現提供參考。