Spring 配置文件敏感数据加密
需求:
配置文件中会有一些敏感数据,例如数据库账号密码,需要非明文存储。
实现方案:
- jasypt : 最常见的方案
- 自定义加密/解密:Spring 的
EnvironmentPostProcessor
- KMS(密钥管理服务):像 AWS KMS、阿里云 KMS 这样的密钥管理服务可以用来加密和解密敏感数据。
项目Demo :我的Demo工程
一、jasypt
- 添加依赖
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>3.0.4</version>
</dependency>
- 配置文件
jasypt.encryptor.password=your-strong-password
spring.datasource.url=jdbc:mysql://localhost:3306/mydb
spring.datasource.username=myuser
spring.datasource.password=ENC(Gs4gAe89RnF6z5yJiQ==)
- 加密/解密工具类
public class JasyptUtil {
public static String encrypt(String text, String password) {
BasicTextEncryptor encryptor = new BasicTextEncryptor();
encryptor.setPassword(password);
return encryptor.encrypt(text);
}
public static String decrypt(String encryptedText, String password) {
BasicTextEncryptor encryptor = new BasicTextEncryptor();
encryptor.setPassword(password);
return encryptor.decrypt(encryptedText);
}
public static String encrypt(String text, String password, String algorithm) {
StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor();
encryptor.setPassword(password);
encryptor.setAlgorithm(algorithm);
return encryptor.encrypt(text);
}
public static String decrypt(String encryptedText, String password, String algorithm) {
StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor();
encryptor.setPassword(password);
encryptor.setAlgorithm(algorithm);
return encryptor.decrypt(encryptedText);
}
}
二、EnvironmentPostProcessor
EnvironmentPostProcessor
是 Spring Boot 提供的一个扩展接口,用于在 Spring Environment 对象完成配置属性加载后,且Spring 容器创建前
,对 Environment 中的属性进行修改或添加。它常用于在应用启动时动态配置环境变量,或在不直接修改配置文件的前提下实现敏感数据解密、配置调整等功能
- 自定义实现
import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.boot.origin.OriginTrackedValue;
import org.springframework.core.env.*;
import org.springframework.util.StringUtils;
public class EncryptEnvironmentPostProcessor implements EnvironmentPostProcessor {
private static final String ENC_PREFIX = "ENC(";
private static final String ENC_SUFFIX = ")";
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
MutablePropertySources propertySources = environment.getPropertySources();
// 遍历每个 PropertySource
for (PropertySource<?> propertySource : propertySources) {
String name = propertySource.getName();
if (propertySource instanceof MapPropertySource
&& (name.contains("application-") || name.contains("application."))) {
Map<String, Object> source = (Map<String, Object>) propertySource.getSource();
Map<String, Object> decryptedProperties = new HashMap<>();
for (Map.Entry<String, Object> entry : source.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
// 使用 toString() 方法而不是直接强制转换
String stringValue = value instanceof OriginTrackedValue
? value.toString()
: (value instanceof String ? (String) value : null);
if (stringValue != null) {
// 检查是否有 ENC() 标记
if (StringUtils.hasText(stringValue) && stringValue.startsWith(ENC_PREFIX) && stringValue.endsWith(ENC_SUFFIX)) {
String encryptedValue = stringValue.substring(ENC_PREFIX.length(), stringValue.length() - ENC_SUFFIX.length());
String decryptedValue = decrypt(encryptedValue);
System.out.println("==========encryptedValue:" + encryptedValue + ";decryptedValue=" + decryptedValue);
decryptedProperties.put(key, decryptedValue);
} else {
decryptedProperties.put(key, stringValue); // 保留未加密的值
}
} else if (value instanceof String[]) {
// 处理数组情况
String[] values = (String[]) value;
for (int i = 0; i < values.length; i++) {
String valueStr = values[i].toString(); // 确保调用 toString()
if (StringUtils.hasText(valueStr) && valueStr.startsWith(ENC_PREFIX) && valueStr.endsWith(ENC_SUFFIX)) {
String encryptedValue = valueStr.substring(ENC_PREFIX.length(), valueStr.length() - ENC_SUFFIX.length());
String decryptedValue = decrypt(encryptedValue);
values[i] = decryptedValue;
}
}
decryptedProperties.put(key, values);
}
}
// 要注意 替换原来的 而不是添加到first or last ,否则 可能会有问题
propertySources.replace(name, new MapPropertySource(name, decryptedProperties));
}
}
}
private String decrypt(String encryptedText) {
// 替换成实际的解密逻辑
return AESUtil.decrypt(encryptedText, "IGJ+xzgyBYMoQQJ6YYa6OQ==");
}
}
- 配置生效
要使 EnvironmentPostProcessor 生效,通常需要在 META-INF/spring.factories 文件中注册它:
org.springframework.boot.env.EnvironmentPostProcessor=com.example.CustomEnvironmentPostProcessor
META-INF/spring.factories
文件是 Spring 框架中用于自动配置和组件扫描的重要机制之一。它主要用于定义自动配置类和初始化器类,使得 Spring Boot 能够自动发现并加载这些类。
要注意的点:
EnvironmentPostProcessor
在 ApplicationContext 初始化之前执行,因此在这个阶段你还不能访问到 ApplicationContext 中的 bean。MutablePropertySources
是 Spring 框架中用于管理多个 PropertySource 对象的容器。每个 PropertySource 都代表一个配置源,例如属性文件、系统环境变量、JVM 系统属性等。- MapPropertySource.getSource() 返回的 Map 是
不可修改
的 UnmodifiableMap。 - Spring Boot 加载配置文件的过程是基于 Environment 和 PropertySource 的概念来实现的,配置文件可以来自多个位置,并且支持不同优先级。所以替换后新的
MapPropertySource
要注意顺序,一般用repalce
,谨慎使用addFirst
、addLast
和addBefore
等。 - 如果有多个 EnvironmentPostProcessor 实现,它们会按照 @Order 注解的顺序执行。如果没有指定
@Order
,则按类名的字母顺序执行。
我的应用配置加载顺序参考:
configurationProperties
servletConfigInitParams
servletContextInitParams
systemProperties
systemEnvironment
random
Config resource 'class path resource [application-dal.properties]' via location 'classpath:application-dal.properties'
Config resource 'class path resource [application-dev.properties]' via location 'optional:classpath:/'
Config resource 'class path resource [application.properties]' via location 'optional:classpath:/'