spring、spring-boot配置文件屬性內容加解密
實際項目開發過程中,我們的應用程序都有很多的配置文件(例如properties或者yml文件等),我們時常會遇到需要對配置文件敏感字段的參數內容進行加密處理(比如數據庫連接密碼、與第三方的通信密鑰等)。
如果采用一定采用傳統的springMVC做係統集成,我們可以繼承PropertyPlaceholderConfigurer類並複寫其converProperty方法,在該方法內一般需要做兩步處理:
- 1、根據參數名propertyName或者根據參數值propertyValue判斷當前是否需要進行內容解密。
- 2、如果需要解密,根據係統設計調用解密處理邏輯,然後調用父級converProperty方法。
按照上麵的思路,我們先實現自己的PropertyPlaceholderConfigurer子類,假如當前我們的需求是要將test.content參數值後麵追加內容“《我是後加的內容》”
@Slf4j
public class MyPropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer {
@Override
protected String convertProperty(String propertyName, String propertyValue) {
//這裏做對屬性的內容的加解密等操作
//TODO something
if (propertyName.equalsIgnoreCase("test.content")) {
log.info("當前即將過濾的內容[" + propertyName + "]=[" + propertyValue + "]");
propertyValue = propertyValue + "《我是後加的內容》";
} else if (propertyName.equalsIgnoreCase("spring.datasource.password")) {
propertyValue = propertyValue.replace("abc", "");
}
return super.convertProperty(propertyName, propertyValue);
}
}
接下來,我們要是實例化我們自定義的PropertyPlaceholderConfigurer,如果我們使用xml方式配置,則代碼如下:
<bean >
<property name="locations">
<list>
<value>classpath:application.poperties</value>
</list>
</property>
</bean>
如果采用javaconfig方式時代碼如下:
@Bean
public PropertyPlaceholderConfigurer propertyPlaceholderConfigurer(){
PropertyPlaceholderConfigurer placeholderConfigurer=new MyPropertyPlaceholderConfigurer();
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource resource = resolver.getResource("classpath:application.properties");
placeholderConfigurer.setLocation(resource);
return placeholderConfigurer;
}
通過上麵的步驟,我們可以在程序的TestService中使用自動注入或者Spring表達式@Value(“$(參數名)”)獲取到的內容就是我們解密之後的內容。我們啟動程序後我們可以看到日誌內容
c.s.b.t.u.MyPropertyPlaceholderConfigurer[16] - 當前即將過濾的內容[test.content]=[測試內容]
c.s.b.t.s.TestService[24] - 當前拿到的testContent=測試內容《我是後加的內容》
遺憾的時,上述方式存在兩個缺點:
- 1、目前隻支持properties配置文件,當我們使用yaml文件後就不生效了,運行會報文件格式異常,如果要生效,必須複寫底層的loadProperties方法分別對不同格式的文件進行解析。但是這樣可能會麻煩的多。
- 2、當我們使用spring boot後,對數據庫相關配置參數解析後,數據庫自動初始化裝配無法成功,即數據庫會在PropertyPlaceholderConfigurer類之前初始化,如果加密內容正好是數據庫連接密碼,那麼程序啟動後會因為數據庫無法連接而報錯,程序自動掛斷。錯誤內容:Failed to initialize pool: Access denied for user 'root'@'localhost' (using password: YES)
麵對以上問題,直接通過修改PropertyPlaceholderConfigurer解決的路子我並沒有去測試,不過接下來我們介紹另外一種方式解決這個問題,那就是spring boot集成jasypt框架實現對配置文件的參數內容加解密。
jasypt是一個java實現的安全框架,最新版本已經不提供PlaceholderConfigurer,所以我們很難直接使用該框架報對properties或者yml文件進行處理,但是國外大神Ulises Bocchio寫了一個spring boot下用的工具包github地址https://github.com/ulisesbocchio/jasypt-spring-boot,該工具包支持使用jasypt框架來處理properties和yml配置文件參數內容的加解密操作,該工具已經發布到了中央倉庫供大家使用。而且文檔信息非常詳細。下麵我們簡單說一下該工具的優勢。
- 1、該工具支持注解方式開啟jasypt功能,以及注解方式引入一個或多個需要處理的配置文件。
- 2、該工具同時支持properties與yml文件的解析處理。
- 3、該工具支持自定義加解密類型和複寫加解密方法。
在早期的版本中jasypt為spring提供過EncryptablePropertyPlaceholderConfigurer與EncryptablePropertySourcesPlaceholderConfigurer兩個類,但是後麵的包中都不包含這兩個類了。如果希望使用這種方式可以參考如下兩篇博客:
最新的1.9.2版本中以上兩個類都已經不存在了。所以推薦使用Ulises Bocchio寫工具包來做係統集成。下麵我們按照其文檔簡單配置一下使用思路。
首先,引入工具包依賴
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>1.14</version>
</dependency>
如果不使用stater,我們可以引入如下包
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot</artifactId>
<version>1.14</version>
</dependency>
默認情況下jasypt采用的算法是PBEWithMD5AndDES,該算法對同一串明文每次加密的密文都不一樣,比較適合做數據加解密。但是該算法必須配置密碼,我們在yml文件配置如下參數
jasypt:
encryptor:
password: e!Jd&ljyJ^e4I5oU
如果想要改變其他配置例如密文的前後綴也可以在這裏配置。
如果上麵引入的包不是starter,那麼需要在啟動類上添加注解@EnableEncryptableProperties以啟動該功能。
然後我們寫一個junit測試類,具體內容如下
public class TestBootTest {
@Autowired
StringEncryptor stringEncryptor;//密碼解碼器自動注入
@Test
public void test() {
System.out.println(stringEncryptor.encrypt("123456"));
}
}
執行後的控製台輸出123456加密後的內容:P7xVJnbrn/MCzyVEOejTRw==
小提示:在控製台下,我們直接使用jasypt工具包進行原始數據加密可以采用如下命令
java -cp jasypt-1.9.2.jar org.jasypt.intf.cli.JasyptPBEStringEncryptionCLI input=原文 password=密碼 algorithm=加密方式
例如,我們要對中文“原文”進行加密,密碼為123456,加密方式為PBEWithMD5AndDES,則具體命令為
java -cp jasypt-1.9.2.jar org.jasypt.intf.cli.JasyptPBEStringEncryptionCLI input=原文 password=123456 algorithm=PBEWithMD5AndDES
執行後會在控製台輸出如下內容
----ENVIRONMENT-----------------
Runtime: Oracle Corporation Java HotSpot(TM) 64-Bit Server VM 24.79-b02
----ARGUMENTS-------------------
algorithm: PBEWithMD5AndDES
input: 原文
password: 123456
----OUTPUT----------------------
BDhzghe2quZtDpX0ZM/e4w==
我們將spring.datasource.password的值配置為ENC(P7xVJnbrn/MCzyVEOejTRw==),為什麼需要這麼配置呢,因為默認情況下jasypt-spring-boot解析密碼的規則是前綴是ENC(後綴是)。我們改造我們的單元測試工具類
public class TestBootTest {
@Autowired
StringEncryptor stringEncryptor;//密碼解碼器自動注入
@Value("${spring.datasource.password}")
private String password;
@Autowired
private UserInfoMapper userInfoMapper;
@Test
public void test() {
// System.out.println(stringEncryptor.encrypt("123456"));
System.out.println("連接數據庫密碼:" + password);
System.out.println("查詢到的信息:"+userInfoMapper.selectByUserId(1));
}
}
啟動單元測試,可以看到日誌輸出了如下內容
連接數據庫密碼:123456
[2017-08-03 22:20:41,653][INFO ] c.z.h.HikariDataSource[95] - HikariPool-1 - Starting...
[2017-08-03 22:20:42,200][INFO ] c.z.h.HikariDataSource[107] - HikariPool-1 - Start completed.
查詢到的信息:UserInfo(id=1, userName=test1, userAge=20, address=四川成都, addTime=2016-06-05 19:32:42)
可以看到采用jasypt-spring-boot-starter工具來集成spring boot與jasypt的功能成功實現了。我們看再看下配置日誌,可以看到默認情況下字符串加密機默認的配置信息
[2017-08-03 22:20:32,008][INFO ] c.u.j.StringEncryptorConfiguration[45] - Encryptor config not found for property jasypt.encryptor.algorithm, using default value: PBEWithMD5AndDES
[2017-08-03 22:20:32,009][INFO ] c.u.j.StringEncryptorConfiguration[45] - Encryptor config not found for property jasypt.encryptor.keyObtentionIterations, using default value: 1000
[2017-08-03 22:20:32,009][INFO ] c.u.j.StringEncryptorConfiguration[45] - Encryptor config not found for property jasypt.encryptor.poolSize, using default value: 1
[2017-08-03 22:20:32,010][INFO ] c.u.j.StringEncryptorConfiguration[45] - Encryptor config not found for property jasypt.encryptor.providerName, using default value: null
[2017-08-03 22:20:32,012][INFO ] c.u.j.StringEncryptorConfiguration[45] - Encryptor config not found for property jasypt.encryptor.saltGeneratorClassname, using default value: org.jasypt.salt.RandomSaltGenerator
[2017-08-03 22:20:32,029][INFO ] c.u.j.StringEncryptorConfiguration[45] - Encryptor config not found for property jasypt.encryptor.stringOutputType, using default value: base64
通過以上方式使用jasypt框架處理配置文件參數加密的功能就基本上OK了,但是存在一定的風險,那就是程序配置文件中,存在解密密文的密碼。因為PBEWithMD5AndDES算法到處都可以找到實現。如果拿到了數據庫密文和算法的密碼,那麼很容易解析出連接數據庫的密碼。一般嚴謹的做法是不會將密文信息與解密工具放在一起,避免程序被獲取後,加密算法和數據庫密碼密文以及解密密碼都同時被泄露。我們公司就是采用C語言寫了一個加解密工具,放在服務器上特定的位置。有Java程序中去調用該工具進行解密。那麼這種情況下我們怎麼自定義解密邏輯呢?
從單元測試中可以看到我們注入了一個加解密的接口StringEncryptor,跟蹤改類的實現可以看到係統調用了encrypt方法用於明文加密,調用decrypt對密文進行解密。於是我們實現該接口,假如我們將數據庫密碼參數spring.datasource.password=123456配置為spring.datasource.password=ENC(654321),那麼在該類裏麵我們要將654321替換為123456,得到我們的真實密碼。代碼如下
public class DefaultEncryptor implements StringEncryptor {
@Override
public String encrypt(String message) {
if ("123456".equalsIgnoreCase(message)) {
message = "654321";
}
return message;
}
@Override
public String decrypt(String encryptedMessage) {
if ("654321".equalsIgnoreCase(encryptedMessage)) {
log.info("將密文[654321]替換為[123456]");
encryptedMessage = "123456";
}
return encryptedMessage;
}
}
接下來,我們使用java config方式實例化該對象替換默認的StringEncryptor實例,如下
@Bean(name = "stringEncryptor")
public StringEncryptor stringEncryptor() {
return new DefaultEncryptor();
}
我們運行單元測試,可以看到自定義密碼解碼器的日誌信息
[2017-08-03 22:31:47,291][INFO ] c.s.b.t.u.DefaultEncryptor[22] - 將密文[654321]替換為[123456]
通過以上步驟,我們采用自定義的StringEncryptor實現了上下文中敏感信息的自定義加解密處理。
特別說明,文中引入的jasypt-spring-boot-starter的版本是1.14,該版本要求JRE版本必須為1.8。如果要想在1.6或者1.7下運行,那麼必須使用1.4或1.5的版本的定製版本。
1.7版本下引入:
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>1.5-java7</version>
</dependency>
1.6版本下引入:
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>1.5-java6</version>
</dependency>
參考:
https://github.com/ulisesbocchio/jasypt-spring-boot
最後更新:2017-08-30 10:32:27