云计算百科
云计算领域专业知识百科平台

SpringBoot 配置文件自动加解密方案

摘要: 在生产环境中,数据库密码、Redis 密码、第三方 API 密钥等敏感信息如果以明文形式存储在配置文件中,将面临严重的安全风险。本文将深入探讨 SpringBoot 配置文件的自动加解密方案,从原理到实战,带你构建一套安全、优雅的配置管理体系。


一、为什么需要配置文件加解密?

在日常开发中,我们的 application.yml 往往长这样:

spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb
username: root
password: MyS3cretP@ssw0rd!
redis:
host: 192.168.1.100
password: RedisP@ss123

# 第三方服务密钥
aliyun:
oss:
access-key-id: LTAI5tXXXXXXXXXXXXXX
access-key-secret: hK7xXXXXXXXXXXXXXXXXXXXXXX

明文配置的风险:

风险场景说明
代码仓库泄露 Git 仓库被公开或内部人员不当访问,敏感信息一览无余
运维审计困难 无法追踪谁在何时查看或修改了敏感配置
合规要求 等保2.0、GDPR 等法规要求敏感数据必须加密存储
环境隔离不足 开发、测试、生产环境密码混用,增加泄露面

因此,我们需要一种方案:在配置文件中存储密文,应用启动时自动解密,对业务代码完全透明。


二、方案选型对比

方案优点缺点适用场景
Jasypt Spring Boot 开箱即用、社区成熟 密钥管理仍需注意 中小型项目
自定义 EnvironmentPostProcessor 灵活可控、无额外依赖 需要自行实现 有定制需求的项目
Spring Cloud Config + Vault 企业级、安全性极高 架构复杂、运维成本高 微服务/大型企业
自定义 PropertySource 轻量、可控 功能有限 简单加密需求

本文将重点讲解 Jasypt 方案 和 自定义 EnvironmentPostProcessor 方案,这两种方案覆盖了 90% 以上的实际使用场景。


三、Jasypt Spring Boot(推荐入门)

3.1 引入依赖

<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>

3.2 配置加密密钥

在 application.yml 中配置加密密钥(Master Password):

jasypt:
encryptor:
algorithm: PBEWithMD5AndDES
iv-generator-classname: org.jasypt.iv.NoIvGenerator
password: ${JASYPT_PASSWORD:myDefaultMasterKey}

⚠️ 重要提示: 生产环境中,jasypt.encryptor.password 不应硬编码在配置文件中,建议通过环境变量或 JVM 参数传入。

3.3 生成密文

编写一个工具类来生成加密后的配置值:

import org.jasypt.encryption.pbe.PooledPBEStringEncryptor;
import org.jasypt.encryption.pbe.config.SimpleStringPBEConfig;

public class JasyptEncryptorUtil {

private static final String ALGORITHM = "PBEWithMD5AndDES";
private static final String MASTER_PASSWORD = "myDefaultMasterKey";

public static void main(String[] args) {
PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
SimpleStringPBEConfig config = new SimpleStringPBEConfig();
config.setPassword(MASTER_PASSWORD);
config.setAlgorithm(ALGORITHM);
config.setKeyObtentionIterations("1000");
config.setPoolSize("1");
config.setProviderName("SunJCE");
config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator");
config.setIvGeneratorClassName("org.jasypt.iv.NoIvGenerator");
config.setStringOutputType("base64");
encryptor.setConfig(config);

// 加密
String plainText = "MyS3cretP@ssw0rd!";
String encrypted = encryptor.encrypt(plainText);
System.out.println("明文: " + plainText);
System.out.println("密文: " + encrypted);

// 解密验证
String decrypted = encryptor.decrypt(encrypted);
System.out.println("解密: " + decrypted);
}
}

运行后输出:

明文: MyS3cretP@ssw0rd!
密文: aB3dE5fG7hI9jK1lM2nO4p==
解密: MyS3cretP@ssw0rd!

3.4 在配置文件中使用密文

将生成的密文用 ENC() 包裹,放入配置文件:

spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb
username: root
password: ENC(aB3dE5fG7hI9jK1lM2nO4p==)
redis:
host: 192.168.1.100
password: ENC(xY9zW8vU7tS6rQ5pO4nM3l==)

启动应用时传入密钥:

# 方式一:JVM 参数
java -jar app.jar –jasypt.encryptor.password=yourMasterKey

# 方式二:环境变量
export JASYPT_PASSWORD=yourMasterKey
java -jar app.jar

# 方式三:系统属性
java -Djasypt.encryptor.password=yourMasterKey -jar app.jar

3.5 Jasypt 的自动解密原理

Jasypt Spring Boot Starter 的工作流程如下:

应用启动


JasyptSpringBootApplicationListener 监听 ApplicationEnvironmentPreparedEvent


遍历所有 PropertySource


发现值匹配 ENC(xxx) 模式的属性


使用配置的 Encryptor 自动解密


将解密后的明文替换回 PropertySource


业务代码通过 @Value 或 Environment 获取到的是明文


四、自定义 EnvironmentPostProcessor(深度定制)

如果你的项目不想引入第三方依赖,或者有更复杂的加解密需求(如使用国密算法 SM4、对接 KMS 密钥管理服务),可以通过自定义 EnvironmentPostProcessor 来实现。

4.1 整体架构设计

┌─────────────────────────────────────────────────┐
│ application.yml │
│ spring.datasource.password=CRYPT:base64:xxx │
└────────────────────┬────────────────────────────┘


┌─────────────────────────────────────────────────┐
│ ConfigDecryptEnvironmentPostProcessor │
│ (实现 EnvironmentPostProcessor 接口) │
│ │
│ 1. 遍历所有 PropertySource │
│ 2. 识别 CRYPT: 前缀的配置项 │
│ 3. 调用 ConfigDecryptor 解密 │
│ 4. 用解密后的值替换原 PropertySource │
└────────────────────┬────────────────────────────┘


┌─────────────────────────────────────────────────┐
│ ConfigDecryptor │
│ (策略接口,支持多种加密算法) │
│ ├── AESConfigDecryptor │
│ ├── SM4ConfigDecryptor │
│ └── KMSConfigDecryptor │
└─────────────────────────────────────────────────┘

4.2 定义解密策略接口

/**
* 配置解密器策略接口
*/

public interface ConfigDecryptor {

/**
* 判断是否支持该加密格式
*/

boolean supports(String encryptedValue);

/**
* 执行解密
*/

String decrypt(String encryptedValue);
}

4.3 实现 AES 解密器

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;

/**
* 基于 AES 的配置解密器
*/

public class AESConfigDecryptor implements ConfigDecryptor {

private static final String PREFIX = "CRYPT:AES:";
private static final String ALGORITHM = "AES";

// 生产环境应从安全存储获取密钥
private final String secretKey;

public AESConfigDecryptor(String secretKey) {
// AES-128 需要 16 字节密钥
this.secretKey = padKey(secretKey, 16);
}

@Override
public boolean supports(String encryptedValue) {
return encryptedValue != null && encryptedValue.startsWith(PREFIX);
}

@Override
public String decrypt(String encryptedValue) {
try {
String cipherText = encryptedValue.substring(PREFIX.length());
byte[] encrypted = Base64.getDecoder().decode(cipherText);

SecretKeySpec keySpec = new SecretKeySpec(secretKey.getBytes(), ALGORITHM);
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, keySpec);

byte[] decrypted = cipher.doFinal(encrypted);
return new String(decrypted, "UTF-8");
} catch (Exception e) {
throw new RuntimeException("配置解密失败: " + encryptedValue, e);
}
}

private String padKey(String key, int length) {
StringBuilder sb = new StringBuilder(key);
while (sb.length() < length) {
sb.append('0');
}
return sb.substring(0, length);
}
}

4.4 实现 EnvironmentPostProcessor

import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* 配置文件自动解密后置处理器
* <p>
* 在 Spring 环境准备完成后,自动扫描并解密配置中的加密属性
* </p>
*/

public class ConfigDecryptEnvironmentPostProcessor implements EnvironmentPostProcessor {

private static final String DECRYPTED_PROPERTY_SOURCE_NAME = "decryptedConfig";

private final List<ConfigDecryptor> decryptors;

public ConfigDecryptEnvironmentPostProcessor() {
// 从环境变量获取密钥,支持多种方式
String aesKey = System.getenv("CONFIG_AES_KEY");
if (aesKey == null) {
aesKey = System.getProperty("config.aes.key", "defaultAESKey1234");
}

this.decryptors = List.of(
new AESConfigDecryptor(aesKey)
// 可扩展: new SM4ConfigDecryptor(…), new KMSConfigDecryptor(…)
);
}

@Override
public void postProcessEnvironment(ConfigurableEnvironment environment,
SpringApplication application) {
Map<String, Object> decryptedProperties = new HashMap<>();
MutablePropertySources propertySources = environment.getPropertySources();

// 遍历所有 PropertySource
for (PropertySource<?> propertySource : propertySources) {
if (!(propertySource instanceof EnumerablePropertySource)) {
continue;
}

EnumerablePropertySource<?> enumerableSource =
(EnumerablePropertySource<?>) propertySource;

for (String propertyName : enumerableSource.getPropertyNames()) {
Object rawValue = enumerableSource.getProperty(propertyName);

if (rawValue instanceof String) {
String value = (String) rawValue;
String decrypted = tryDecrypt(value);

if (decrypted != null) {
decryptedProperties.put(propertyName, decrypted);
}
}
}
}

// 将解密后的属性添加到最高优先级,覆盖原始加密值
if (!decryptedProperties.isEmpty()) {
propertySources.addFirst(
new MapPropertySource(DECRYPTED_PROPERTY_SOURCE_NAME, decryptedProperties)
);
}
}

/**
* 尝试使用所有注册的解密器进行解密
*/

private String tryDecrypt(String value) {
for (ConfigDecryptor decryptor : decryptors) {
if (decryptor.supports(value)) {
return decryptor.decrypt(value);
}
}
return null; // 不需要解密
}
}

4.5 注册 SPI

创建文件 src/main/resources/META-INF/spring.factories(Spring Boot 2.x):

org.springframework.boot.env.EnvironmentPostProcessor=\\
com.example.config.ConfigDecryptEnvironmentPostProcessor

如果是 Spring Boot 3.x,创建文件 src/main/resources/META-INF/spring/org.springframework.boot.env.EnvironmentPostProcessor.imports:

com.example.config.ConfigDecryptEnvironmentPostProcessor

4.6 使用加密配置

spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb
username: root
# 使用 CRYPT:AES: 前缀标识需要解密的值
password: CRYPT:AES:aB3dE5fG7hI9jK1lM2nO4p==
redis:
password: CRYPT:AES:xY9zW8vU7tS6rQ5pO4nM3l==

# 自定义配置同样适用
aliyun:
oss:
access-key-secret: CRYPT:AES:pQ3rS5tU7vW9xY1zA2bC4d==

启动应用:

# 通过环境变量传入 AES 密钥
export CONFIG_AES_KEY=yourProductionKey
java -jar app.jar

# 或通过 JVM 参数
java -Dconfig.aes.key=yourProductionKey -jar app.jar

4.7 加密工具类

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;

/**
* 配置加密工具 – 用于生成加密后的配置值
*/

public class ConfigEncryptUtil {

private static final String ALGORITHM = "AES";

public static String encrypt(String plainText, String secretKey) throws Exception {
String paddedKey = padKey(secretKey, 16);
SecretKeySpec keySpec = new SecretKeySpec(paddedKey.getBytes(), ALGORITHM);
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, keySpec);

byte[] encrypted = cipher.doFinal(plainText.getBytes("UTF-8"));
return "CRYPT:AES:" + Base64.getEncoder().encodeToString(encrypted);
}

private static String padKey(String key, int length) {
StringBuilder sb = new StringBuilder(key);
while (sb.length() < length) {
sb.append('0');
}
return sb.substring(0, length);
}

public static void main(String[] args) throws Exception {
String key = "yourProductionKey";

System.out.println(encrypt("MyS3cretP@ssw0rd!", key));
// 输出: CRYPT:AES:xxxxxxxxxxxxxxxxxx==

System.out.println(encrypt("RedisP@ss123", key));
// 输出: CRYPT:AES:yyyyyyyyyyyyyyyyyy==
}
}


五、结合 Maven Profile 实现多环境密钥隔离

在实际项目中,不同环境应使用不同的加密密钥:

5.1 Maven 配置

<profiles>
<profile>
<id>dev</id>
<properties>
<config.master.key>devKey1234567890</config.master.key>
</properties>
</profile>
<profile>
<id>prod</id>
<properties>
<config.master.key>${env.PROD_MASTER_KEY}</config.master.key>
</properties>
</profile>
</profiles>

5.2 在 application.yml 中引用

config:
encrypt:
master-key: @config.master.key@

5.3 构建命令

# 开发环境
mvn clean package -P dev

# 生产环境(需要设置环境变量 PROD_MASTER_KEY)
mvn clean package -P prod


六、进阶:集成 KMS 密钥管理服务

对于安全性要求极高的场景,可以将密钥托管到云厂商的 KMS 服务:

/**
* 基于阿里云 KMS 的配置解密器
*/

public class KMSConfigDecryptor implements ConfigDecryptor {

private static final String PREFIX = "CRYPT:KMS:";
private final KmsClient kmsClient;

public KMSConfigDecryptor(KmsClient kmsClient) {
this.kmsClient = kmsClient;
}

@Override
public boolean supports(String encryptedValue) {
return encryptedValue != null && encryptedValue.startsWith(PREFIX);
}

@Override
public String decrypt(String encryptedValue) {
String cipherText = encryptedValue.substring(PREFIX.length());

DecryptRequest request = new DecryptRequest();
request.setCiphertextBlob(cipherText);

DecryptResponse response = kmsClient.decrypt(request);
return new String(
Base64.getDecoder().decode(response.getPlaintext())
);
}
}

配置文件中使用:

spring:
datasource:
password: CRYPT:KMS:Y2lwaGVydGV4dC1mcm9tLWttcw==


七、安全最佳实践

7.1 密钥管理清单

✅ 推荐做法:
├── 通过环境变量注入密钥
├── 使用 KMS/Vault 等密钥管理服务
├── 不同环境使用不同密钥
├── 定期轮换密钥
├── 限制密钥的知悉范围
└── 密钥与代码仓库完全隔离

❌ 避免做法:
├── 密钥硬编码在代码中
├── 密钥明文写在配置文件中
├── 所有环境共用一个密钥
├── 密钥通过聊天工具传递
└── 密钥长期不更换

7.2 日志脱敏

即使配置已加密,也要注意日志中不要打印明文密码:

import ch.qos.logback.classic.pattern.MessageConverter;
import ch.qos.logback.classic.spi.ILoggingEvent;

/**
* 日志脱敏转换器 – 自动屏蔽日志中的敏感信息
*/

public class SensitiveInfoConverter extends MessageConverter {

private static final Pattern PASSWORD_PATTERN =
Pattern.compile("(password|secret|key)\\\\s*[:=]\\\\s*\\\\S+", Pattern.CASE_INSENSITIVE);

@Override
public String convert(ILoggingEvent event) {
String message = event.getFormattedMessage();
return PASSWORD_PATTERN.matcher(message).replaceAll("$1=******");
}
}

7.3 Git 仓库安全

# .gitignore – 确保以下内容不被提交
*.env
applicationlocal.yml
**/secrets/

配合 git-secrets 工具防止敏感信息意外提交:

# 安装 git-secrets
brew install git-secrets

# 配置禁止提交包含特定模式的文件
git secrets –add 'jasypt\\.encryptor\\.password\\s*=\\s*\\S+'
git secrets –add 'CRYPT:[A-Z]+:[A-Za-z0-9+/=]+'


八、方案对比总结

维度Jasypt自定义 PostProcessorKMS 集成
接入成本 极低 中等 较高
灵活性 一般
安全性 中等 中等
运维复杂度
算法可替换 有限 完全自由 完全自由
社区支持 成熟 自行维护 依赖云厂商

选型建议:

  • 中小型项目 / 快速接入 → Jasypt Spring Boot
  • 有定制需求 / 国密合规 → 自定义 EnvironmentPostProcessor
  • 微服务架构 / 高安全要求 → KMS + Vault 集成

九、常见问题 FAQ

Q1: 加密后的配置文件还能在 IDE 中正常启动吗?

可以。只需在 IDE 的 Run Configuration 中配置对应的环境变量或 VM Options 即可:

VM Options: -Djasypt.encryptor.password=devKey123
Environment Variables: CONFIG_AES_KEY=devKey123

Q2: 如何验证配置是否正确解密?

添加一个启动检查器:

@Component
public class ConfigDecryptVerifier implements ApplicationRunner {

@Value("${spring.datasource.password}")
private String datasourcePassword;

@Override
public void run(ApplicationArguments args) {
if (datasourcePassword != null && datasourcePassword.startsWith("CRYPT:")) {
throw new IllegalStateException("配置解密失败!请检查密钥配置。");
}
// 也可以检查是否仍然是 ENC() 格式
if (datasourcePassword != null && datasourcePassword.startsWith("ENC(")) {
throw new IllegalStateException("Jasypt 解密失败!请检查密钥配置。");
}
System.out.println("✅ 配置解密验证通过");
}
}

Q3: 加密后 Nacos/Apollo 等配置中心还适用吗?

完全适用。EnvironmentPostProcessor 在 Spring 环境初始化后执行,无论是本地配置文件还是远程配置中心加载的属性,都会被扫描和解密。只需确保密文格式一致即可。

Q4: 如何实现密钥的自动轮换?

支持"双密钥"过渡期:

@Override
public String decrypt(String encryptedValue) {
try {
// 先用新密钥解密
return decryptWithKey(encryptedValue, newKey);
} catch (Exception e) {
// 新密钥失败,回退到旧密钥
return decryptWithKey(encryptedValue, oldKey);
}
}


🎁 福利时间

如果你正在备战面试或者想要学习其他知识,给大家推荐一个宝藏知识库,作者整理了一些列 Java 程序员需要掌握的核心知识,有需要的自取不谢。

知识库地址:https://farerboy.com/


赞(0)
未经允许不得转载:网硕互联帮助中心 » SpringBoot 配置文件自动加解密方案
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!