一、技术架构
1.1 核心依赖
xml
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>2023.0.3.4</version>
</dependency>
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
二、Nacos配置中心加解密
2.1 自定义StringEncryptor实现
java
@Component("ymlDecryptConfig")
public class YmlDecryptConfig implements StringEncryptor {
@Resource
private Sm4EncryptUtils sm4EncryptUtils;
@Override
public String encrypt(String message) {
return sm4EncryptUtils.encrypt(message);
}
@Override
public String decrypt(String encryptedMessage) {
return sm4EncryptUtils.decrypt(encryptedMessage);
}
}
2.2 配置文件配置
yaml
jasypt:
encryptor:
bean: ymlDecryptConfig
spring:
cloud:
nacos:
config:
username: ENC(加密字符串)
password: ENC(加密字符串)
三、本地配置文件加解密实现
3.1 核心原理分析
BindHandler接口作用
java
// Spring Boot 2.4+ 引入的配置绑定机制
public interface BindHandler {
// 在绑定属性成功后调用,可以对结果进行处理
Object onSuccess(ConfigurationPropertyName name,
Bindable<?> target,
BindContext context,
Object result);
// 绑定前、绑定失败等方法
// ...
}
TextEncryptorBindHandler分析
在spring-cloud-context包中:
java
// Spring Cloud 内置的解密处理器
public class TextEncryptorBindHandler extends AbstractBindHandler {
@Override
public Object onSuccess(ConfigurationPropertyName name,
Bindable<?> target,
BindContext context,
Object result) {
if (result instanceof String) {
String value = (String) result;
// 检查是否有加密前缀,如"ENC(...)"
if (value.startsWith("ENC(") && value.endsWith(")")) {
// 调用TextEncryptor进行解密
return textEncryptor.decrypt(extractEncryptedValue(value));
}
}
return result;
}
}
3.2 自定义BindHandler实现
java
public class CustomBindHandler extends AbstractBindHandler {
private static final String ENV_KEY = "CONFIG_ENCRYPT_KEY";
private static final String PREFIX = "ENC(";
private static final String SUFFIX = ")";
private final YmlDecryptConfig decryptConfig;
public CustomBindHandler() {
// 从环境变量获取密钥
String key = System.getenv(ENV_KEY);
if (key == null) {
key = System.getProperty(ENV_KEY.toLowerCase().replace("_", "."));
}
// 创建解密器
this.decryptConfig = createDecryptConfig(key);
}
@Override
public Object onSuccess(ConfigurationPropertyName name,
Bindable<?> target,
BindContext context,
Object result) {
if (result instanceof String) {
String value = (String) result;
// 匹配ENC(...)格式
if (value.startsWith(PREFIX) && value.endsWith(SUFFIX)) {
// 提取并解密
String encrypted = value.substring(PREFIX.length(),
value.length() - SUFFIX.length());
return decryptConfig.decrypt(encrypted);
}
}
return result;
}
private YmlDecryptConfig createDecryptConfig(String key) {
YmlDecryptConfig config = new YmlDecryptConfig();
Sm4EncryptUtils utils = new Sm4EncryptUtils();
utils.setPrivateKey(key);
config.setSm4EncryptUtils(utils);
return config;
}
}
3.3 注册自定义处理器
java
@Configuration
public class BootstrapConfiguration {
@Bean
@ConditionalOnMissingBean
public BindHandler bindHandler() {
return new CustomBindHandler();
}
}
在META-INF/spring.factories中:
properties
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
com.example.config.BootstrapConfiguration
四、底层机制解析
4.1 配置加载流程
1. SpringApplication启动
2. 加载bootstrap配置文件
3. PropertySourceLoader解析配置文件
4. Binder进行属性绑定
5. BindHandler链式处理
6. TextEncryptorBindHandler/CustomBindHandler解密
7. 将解密后的值注入Bean
4.2 BindHandler工作机制
java
// Binder中的关键代码
public <T> T bindOrCreate(ConfigurationPropertyName name, Bindable<T> target,
BindHandler handler) {
// 创建绑定上下文
BindContext context = new ApplicationBindContext();
// 执行绑定
return bind(name, target, handler, context, false);
}
private <T> T bind(ConfigurationPropertyName name, Bindable<T> target,
BindHandler handler, BindContext context, boolean allowRecursiveBinding) {
// 调用BindHandler的onStart方法
handler.onStart(name, target, context);
try {
// 执行实际绑定
T result = doBind(name, target, context, allowRecursiveBinding);
// 调用onSuccess方法(解密在这里发生)
result = handler.onSuccess(name, target, context, result);
return result;
} catch (Exception ex) {
// 处理异常
return handler.onFailure(name, target, context, ex);
} finally {
// 清理
handler.onFinish(name, target, context);
}
}
4.3 TextEncryptorBindHandler vs CustomBindHandler
| 特性 | TextEncryptorBindHandler | CustomBindHandler |
|---|---|---|
| 解密时机 | 属性绑定成功后 | 属性绑定成功后 |
| 加密前缀 | 可配置 | ENC(...)固定格式 |
| 加密算法 | Jasypt默认 | SM4国密算法 |
| 密钥来源 | jasypt.encryptor.password | 环境变量 |
| 集成方式 | Spring Cloud内置 | 自定义实现 |
4.4 Spring Cloud 2023.0.3.4版本适配
java
// 新版Spring Cloud中,TextEncryptorBindHandler已被重构
// 核心逻辑移动到PropertySourceBootstrapConfiguration中
public class PropertySourceBootstrapConfiguration
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext context) {
// 加载PropertySource
List<PropertySource<?>> sources = loadPropertySources();
// 应用解密处理器
for (PropertySource<?> source : sources) {
if (source instanceof OriginTrackedMapPropertySource) {
// 解密处理
decryptPropertySource((OriginTrackedMapPropertySource) source);
}
}
}
}
五、完整示例配置
5.1 bootstrap.yml
yaml
spring:
application:
name: demo-service
cloud:
nacos:
config:
server-addr: ${NACOS_HOST:localhost}:8848
username: ${NACOS_USER:ENC(加密用户名)}
password: ${NACOS_PASS:ENC(加密密码)}
datasource:
url: jdbc:mysql://localhost:3306/demo
username: ENC(加密用户名)
password: ENC(加密密码)
redis:
password: ENC(加密Redis密码)
5.2 启动参数
bash
# 设置加密密钥
export CONFIG_ENCRYPT_KEY=your-32bit-sm4-key
# 启动应用
java -jar app.jar \
--spring.profiles.active=prod \
--CONFIG.ENCRYPT.KEY=${CONFIG_ENCRYPT_KEY}
六、关键注意事项
- 密钥安全:生产环境密钥必须通过环境变量或密钥管理服务传入
- 加密格式 :必须使用
ENC(加密内容)格式包裹 - 处理器顺序:确保CustomBindHandler在绑定链中的正确顺序
- 版本兼容:Spring Cloud 2023.0.3.4与Spring Boot 3.x的兼容性已验证
- 性能影响:加解密操作在启动时一次性完成,不影响运行时性能
七、总结
通过自定义StringEncryptor和BindHandler,实现了Nacos配置中心和本地配置文件的双重加密保护。关键点在于理解Spring Boot的配置绑定机制和Spring Cloud的解密处理流程,特别是BindHandler的onSuccess方法在属性绑定后的处理时机。