SpringBoot中的敏感信息的配置進(jìn)行加密處理,這種方式你知道嗎?
環(huán)境:Springboot2.4.12 + Spring Cloud Context 3.0.5
概述
SpringBoot配置文件中的內(nèi)容通常情況下是明文顯示,安全性就比較低一些。在application.properties或application.yml,比如數(shù)據(jù)庫(kù)配置信息的密碼,Redis配置的密碼等都是通過(guò)明文配置的,為了提供系統(tǒng)整體的安全性,我們需要對(duì)這些敏感的信息進(jìn)行加密處理,這樣即便你難道了我的配置信息你也獲取不到有價(jià)值的信息。
在Springboot下我們可以通過(guò)如下兩種方式簡(jiǎn)單的實(shí)現(xiàn)敏感信息的加密處理:
- Jasypt這是國(guó)外的一個(gè)開(kāi)源加密工具包,功能強(qiáng)大。
- 基于EnvironmentPostProcessor實(shí)現(xiàn)我們可以通過(guò)實(shí)現(xiàn)這么一個(gè)接口來(lái)實(shí)現(xiàn)自己的加解密處理,我們也可以通過(guò)引入spring-cloud-context包,在該包中提供了一個(gè)DecryptEnvironmentPostProcessor處理器進(jìn)行對(duì)加密信息進(jìn)行解密。
關(guān)于Jasypt網(wǎng)上介紹的很多,這里不做介紹。這里我們主要只講Spring Cloud Context中提供的這個(gè)EnvironmentPostprocessor。
環(huán)境配置
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-context</artifactId>
<version>3.0.5</version>
</dependency>
應(yīng)用配置
encrypt:
key: 123456789 #密鑰
salt: abcdef #加密的內(nèi)容使用了加鹽處理
---
spring:
cloud:
decrypt-environment-post-processor:
enabled: true #開(kāi)啟解密功能
程序代碼
現(xiàn)在需要對(duì)custom.password這樣的一個(gè)key值進(jìn)行加密處理
custom:
password: 123456
首先需要對(duì)這明文生成加密的內(nèi)容,如下方式:
public static void main(String[] args) throws Exception {
String key = "123456789" ;
String salt = "abcdef" ;
String text = "123123" ;
KeyProperties keyProperties = new KeyProperties() ;
keyProperties.setKey(key) ;
keyProperties.setSalt(salt) ;
String result = TextEncryptorUtils.createTextEncryptor(keyProperties, null).encrypt(text) ;
System.out.println(result) ;
}
通過(guò)上面的代碼就可以生成加密信息,接下來(lái)就是將加密后的內(nèi)容配置到配置文件中
custom:
password: "{cipher}2a483a44681006be6f0730b1acb45325c6bd20abe37369ef4fdb52c2e194a365"
注意:這里的內(nèi)容是有格式要求的必須是:{cipher}開(kāi)頭。這里還有一點(diǎn)需要注意有可能你運(yùn)行上面的代碼會(huì)報(bào)錯(cuò),報(bào)錯(cuò)信息應(yīng)該是 “Illegal key size” (非法的密鑰大小),比如:AES加密算法在默認(rèn)JDK(Sun的JDK)配置情況下支持的密鑰大小是128位,一旦超過(guò)就會(huì)報(bào)錯(cuò),這時(shí)候你需要安裝Java加密擴(kuò)展(JCE)策略文件。下面提供了3個(gè)版本的JCE策略文件。
Java 6 JCE:
https://www.oracle.com/technetwork/java/javase/downloads/jce-6-download-429243.html
Java 7 JCE
https://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html
Java 8 JCE
https://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html
下載對(duì)應(yīng)的版本后將其解壓如下位置:
JCE策略文件安裝目錄
以上操作完成以后在運(yùn)行上面的程序就能輸出結(jié)果了,然后將結(jié)果替換配置文件內(nèi)容即可。
接下來(lái)測(cè)試:
@Value("${custom.password}")
public String pwd ;
@GetMapping("/pwd")
public String pwd() {
return pwd ;
}
正確的輸出了我們的內(nèi)容
原理
關(guān)于EnvironmentPostProcessor接口編寫完成以后都會(huì)進(jìn)行配置,我們上面引入的context包就對(duì)DecryptEnvironmentPostProcessor進(jìn)行了配置:
配置EnvironmentPostProcessor
public class DecryptEnvironmentPostProcessor extends AbstractEnvironmentDecrypt implements EnvironmentPostProcessor, Ordered {
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
// 判斷是否開(kāi)啟了功能
if (bootstrapEnabled(environment) || useLegacyProcessing(environment) || !isEnabled(environment)) {
return;
}
// 如果環(huán)境中不存在TextEncryptor那么也不會(huì)開(kāi)啟
if (!ClassUtils.isPresent("org.springframework.security.crypto.encrypt.TextEncryptor", null)) {
return;
}
// 獲取當(dāng)前環(huán)境下的所有配置屬性
MutablePropertySources propertySources = environment.getPropertySources();
environment.getPropertySources().remove(DECRYPTED_PROPERTY_SOURCE_NAME);
// 解密所有的配置屬性,這里也就是入口了
Map<String, Object> map = TextEncryptorUtils.decrypt(this, environment, propertySources);
if (!map.isEmpty()) {
// 將解密后的配置添加到環(huán)境中(addFirst 表示了添加到具有最高優(yōu)先級(jí)的給定屬性源對(duì)象)
propertySources.addFirst(new SystemEnvironmentPropertySource(DECRYPTED_PROPERTY_SOURCE_NAME, map));
}
}
protected Boolean isEnabled(ConfigurableEnvironment environment) {
// 獲取配置屬性值,是否開(kāi)啟功能
return environment.getProperty("spring.cloud.decrypt-environment-post-processor.enabled", Boolean.class, true);
}
}
解密配置
public abstract class TextEncryptorUtils {
static Map<String, Object> decrypt(AbstractEnvironmentDecrypt decryptor, ConfigurableEnvironment environment, MutablePropertySources propertySources) {
// 獲取加解密服務(wù)接口
TextEncryptor encryptor = getTextEncryptor(decryptor, environment);
return decryptor.decrypt(encryptor, propertySources);
}
static TextEncryptor getTextEncryptor(AbstractEnvironmentDecrypt decryptor, ConfigurableEnvironment environment) {
Binder binder = Binder.get(environment);
// 將屬性配置以 ‘encrypt’開(kāi)頭的都綁定到KeyProperties對(duì)象中
KeyProperties keyProperties = binder.bind(KeyProperties.PREFIX, KeyProperties.class).orElseGet(KeyProperties::new);
// 檢查是否配置了encrypt.key 等核心的信息
if (TextEncryptorUtils.keysConfigured(keyProperties)) {
decryptor.setFailOnError(keyProperties.isFailOnError());
// 檢查是否當(dāng)前CLASSPATH中是否存在RsaSecretEncryptor
if (ClassUtils.isPresent("org.springframework.security.rsa.crypto.RsaSecretEncryptor", null)) {
RsaProperties rsaProperties = binder.bind(RsaProperties.PREFIX, RsaProperties.class).orElseGet(RsaProperties::new);
return TextEncryptorUtils.createTextEncryptor(keyProperties, rsaProperties);
}
// 如果你沒(méi)有使用及配置Rsa相關(guān)的,那么就會(huì)在這里創(chuàng)建加解密服務(wù)接口,這里跟蹤了下使用的是AES加密算法
return new EncryptorFactory(keyProperties.getSalt()).create(keyProperties.getKey());
}
// no keys configured
return new TextEncryptorUtils.FailsafeTextEncryptor();
}
}
獲取到了加解密服務(wù)接口以后,接下來(lái)就是對(duì)配置屬性進(jìn)行解密操作。
public class AbstractEnvironmentDecrypt {
public static final String ENCRYPTED_PROPERTY_PREFIX = "{cipher}";
protected Map<String, Object> decrypt(TextEncryptor encryptor, PropertySources propertySources) {
Map<String, Object> properties = merge(propertySources);
// 解密處理
decrypt(encryptor, properties);
return properties;
}
protected void decrypt(TextEncryptor encryptor, Map<String, Object> properties) {
// 開(kāi)始替換所有以{cipher}開(kāi)頭的屬性值
properties.replaceAll((key, value) -> {
String valueString = value.toString();
if (!valueString.startsWith(ENCRYPTED_PROPERTY_PREFIX)) {
return value;
}
// 解密數(shù)據(jù), key 配置屬性的key, valueString要解密的數(shù)據(jù)
return decrypt(encryptor, key, valueString);
});
}
protected String decrypt(TextEncryptor encryptor, String key, String original) {
// 截取{cipher}之后的內(nèi)容
String value = original.substring(ENCRYPTED_PROPERTY_PREFIX.length());
try {
// 解密數(shù)據(jù)
value = encryptor.decrypt(value);
return value;
} catch (Exception e) {
// ...
return "";
}
}
}
整個(gè)源碼處理還是非常簡(jiǎn)單的。