Spring源码探析(二):BootstrapContext初始化深度解析(默认配置文件加密实现原理)
BootstrapContext是Spring Boot 2.4 版本后引入的全新引导上下文机制,取代了早期版本中基于BootstrapApplicationListener的配置加载模式。基于SpringClould开始了解BootstrapContext初始化。
创建DefaultBootstrapContext
private DefaultBootstrapContext createBootstrapContext() {
DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();
// 使用BootstrapRegistryInitializer初始化DefaultBootstrapContext,SpringCloud默认提供了两个初始化程序,自己也可以实现这个接口来自定义自己的逻辑
this.bootstrapRegistryInitializers.forEach((initializer) -> initializer.initialize(bootstrapContext));
return bootstrapContext;
}
默认提供的两个初始化程序
RefreshBootstrapRegistryInitializer
@Override
public void initialize(BootstrapRegistry registry) {
// promote BootstrapContext to context
// 注册了一个BootstrpContext close的监听器
registry.addCloseListener(event -> {
BootstrapContext bootstrapContext = event.getBootstrapContext();
// 将bootstrapContext注册到BeanFactory中 这样我们就能通过依赖注入获取BootstrapContext
event.getApplicationContext().getBeanFactory().registerSingleton("bootstrapContext", bootstrapContext);
});
}
TextEncryptorConfigBootstrapper
这个用于注册文本加密器使用,可以用来实现配置文件的加密功能。
使用示例
// 用来测试解密的属性
@Component
@ConfigurationProperties(prefix = "test")
@Data
public class TestConfig {
/**
* testName
*/
private String testName;
}
@Component
@RequiredArgsConstructor
public class TestTextEncryptor implements ApplicationRunner {
// 文本加密器
private final TextEncryptor textEncryptor;
// Environment
private final Environment environment;
// BootstrapContext
private final BootstrapContext bootstrapContext;
@Override
public void run(ApplicationArguments args) throws Exception {
// 加密字符串 这个配置在配置文件中
System.out.println(textEncryptor.encrypt("加密测试"));
// 将属性绑定到TestConfig类中 bootstrapContext中提供了BindHandler 可以直接在绑定属性的时候实现解密
TestConfig test = Binder.get(environment).bindOrCreate("test", Bindable.of(TestConfig.class), bootstrapContext.get(BindHandler.class));
System.out.println("test = " + test);
}
}
application.yml配置
# 这里配置RSA密钥 这样就会启用RSA的加密器 不包含这个RSA PRIVATE KEY使用Aes加密器
encrypt:
key: '-----BEGIN RSA PRIVATE KEY-----MIICXAIBAAKBgQDfgBRrvCH0U24TLv38RtESTU9r3KmaxZ9s49sKcLBAch0+OWQfHNidyQA1z+uGdJpU2Bgh0Is8/vWBae1Q3o/map2vShXv6BwE3aWzfF1mz6ZoYtb1tEy/0OBZ6Sa5K0gdFGM/czfG4tBF9anZ3N6h0WBW1/sR2ZnIViJtQJCy7QIDAQABAoGAZFPPVulV6KKG+A+RLezwLyILM+UTMYni3fOOwSoCxHs1S1hh7GF7j6DJ+l4CYRH4sXtrocpGprPgqx5MzI+L0lFrLPL+XldfMJkPjtP0tBAS3e9CtbO2cuY9sBkE/KbxfW5Uwfe4X6iy7s1MO+oynR+6ITDTjJRUgk8alGAKJYECQQDvrJs3GzttkrM9woDEAP0ZZOp49RJd526AXmEmPW8W8YvHRrSkWl8IUiA8ciwJzqTFkBbd0IWsijPljXLafrlhAkEA7rlvfpD7smpFWBQRmK59HD4C0x8UcDrzn8+z1iRDxW2vZwqYrLc+pkkAMLy6hEepACjTrJvZ11aL8QFEwmjpDQJAWuhjb0F7BxKvves6oB2n4qvua7a5IrkXpsUloDWJH3C7Dfj5p6VHioZsB8FehtHEmdMPeU8QhONez+EZAVszwQJBAI9ns9N7MsgN0NRFUgC/KQbzNW0v6W41663f7q9AH7oU1t52Xhq7BZaMmeGtLfpStfITlHzHLsiOBAjl8zE6Jm0CQAENwuMcWcFmJaeVDTbuTuliiicBuD4rBdnz1iaXa8mhaehRljpsNiiLMDDMuEi5JoGKwmZ8OzbW4NN3NiBsO8M=-----END RSA PRIVATE KEY-----'
# 用来测试绑定的属性 就是上面输出的加密内容{cipher}前缀表示需要进行解密
test:
test-name: '{cipher}AIDOSDRLvdEpXz3z+SeHKrZdWvXDkwy/JrXz/QpU7Z6VgtKKdEeMv8ElToOw7m1IcxJHi49bGBCCjjRCXli1KTn2bQbdnRJ0j7grr4xEhBVuSlgq7M3xVEfWgPvx/dIbvmqNNz2dr6tdHiZS9kiSgsdKW/3aRwWNBPwcbKpis3pB4lcW9+mbJghpH5I5XrtPRivZ4fjozVfn87m/XKZA2++K'
实现效果:
这里使用其实是自己手动绑定配置类,但SpringCloud默认也会将Environment中的配置进行解密,通过DecryptEnvironmentPostProcessor(这个会在后续介绍),而不需要我们自己手动使用。
当我们禁用时,默认绑定是不会进行解密的。
TextEncryptorConfigBootstrapper更多只是暴露出来配置的加密器,供应用程序使用。
源码解析
@Override
public void initialize(BootstrapRegistry registry) {
// 不存在TextEncryptor跳过
if (!ClassUtils.isPresent("org.springframework.security.crypto.encrypt.TextEncryptor", null)) {
return;
}
// 不存在KeyProperties时 注册KeyProperties到BootsrtapContext中 注入的是InstanceSupplier,用来延迟获取对象
registry.registerIfAbsent(KeyProperties.class, context -> context.get(Binder.class)
.bind(KeyProperties.PREFIX, KeyProperties.class).orElseGet(KeyProperties::new));
if (RSA_IS_PRESENT) {
// 注册RSA的配置属性
registry.registerIfAbsent(RsaProperties.class, context -> context.get(Binder.class)
.bind(RsaProperties.PREFIX, RsaProperties.class).orElseGet(RsaProperties::new));
}
// 注册TextEncryptor和TextEncryptorBindHandler TextEncryptor用于文本加密 TextEncryptorBindHandler用于使用Binder进行属性绑定时解密属性
TextEncryptorUtils.register(registry);
// promote beans to context
// 添加BootsrapContext close监听器 将注册到BootsrapContext中的实例 注册到ApplicationContext中成为单例 供应用程序注入
registry.addCloseListener(event -> {
// 如果还是启用的2.4版本以前的Bootstrap加载的 这里就会跳过
if (TextEncryptorUtils.isLegacyBootstrap(event.getApplicationContext().getEnvironment())) {
return;
}
// 将KeyProperties和RsaProperties注册到BeanFactory中
BootstrapContext bootstrapContext = event.getBootstrapContext();
KeyProperties keyProperties = bootstrapContext.get(KeyProperties.class);
ConfigurableListableBeanFactory beanFactory = event.getApplicationContext().getBeanFactory();
if (keyProperties != null) {
beanFactory.registerSingleton("keyProperties", keyProperties);
}
if (RSA_IS_PRESENT) {
RsaProperties rsaProperties = bootstrapContext.get(RsaProperties.class);
if (rsaProperties != null) {
beanFactory.registerSingleton("rsaProperties", rsaProperties);
}
}
// 注册TextEncryptor单例bean
TextEncryptorUtils.promote(bootstrapContext, beanFactory);
});
}
TextEncryptorUtils
public static void register(BootstrapRegistry registry) {
// 注册TextEncryptor
registry.registerIfAbsent(TextEncryptor.class, context -> {
KeyProperties keyProperties = context.get(KeyProperties.class);
// KeyProperties中是否配置了key或者密钥文件location
if (TextEncryptorConfigBootstrapper.keysConfigured(keyProperties)) {
if (TextEncryptorConfigBootstrapper.RSA_IS_PRESENT) {
// 存在RsaSecretEncryptor 获取RsaProperties构建加密器
RsaProperties rsaProperties = context.get(RsaProperties.class);
// 创建加密器
return createTextEncryptor(keyProperties, rsaProperties);
}
return new EncryptorFactory(keyProperties.getSalt()).create(keyProperties.getKey());
}
// 没有配置密钥的话 会默认注册一个不支持的文本加密器 使用加密和解密时抛出异常
return new FailsafeTextEncryptor();
});
// 注册TextEncryptorBindHandler
registry.registerIfAbsent(BindHandler.class, context -> {
TextEncryptor textEncryptor = context.get(TextEncryptor.class);
if (textEncryptor != null) {
KeyProperties keyProperties = context.get(KeyProperties.class);
return new TextEncryptorBindHandler(textEncryptor, keyProperties);
}
return null;
});
}
public static void promote(BootstrapContext bootstrapContext, ConfigurableListableBeanFactory beanFactory) {
// 将BootstrapContext中的文本加密器注册单例到BeanFactory中
// 这里只注册了TextEncryptor 应用程序需要TextEncryptorBindHandler的话 可以通过BootstrapContext获取或者自己实现一个BootstrapRegistryInitializer 把他注入到BeanFactory中
TextEncryptor textEncryptor = bootstrapContext.get(TextEncryptor.class);
if (textEncryptor != null) {
beanFactory.registerSingleton("textEncryptor", textEncryptor);
}
}
public static TextEncryptor createTextEncryptor(KeyProperties keyProperties, RsaProperties rsaProperties) {
KeyProperties.KeyStore keyStore = keyProperties.getKeyStore();
if (keyStore.getLocation() != null) {
if (keyStore.getLocation().exists()) {
// 存在密钥文件时 构建Rsa密钥加密器 这个实现是随机生成Aes密钥 Aes密钥加密内容 Rsa公钥加密Aes密钥 将加密后密钥和Aes加密后类容一起Base64
return new RsaSecretEncryptor(
new KeyStoreKeyFactory(keyStore.getLocation(), keyStore.getPassword().toCharArray(),
keyStore.getType()).getKeyPair(keyStore.getAlias(), keyStore.getSecret().toCharArray()),
rsaProperties.getAlgorithm(), rsaProperties.getSalt(), rsaProperties.isStrong());
}
throw new IllegalStateException("Invalid keystore location");
}
// 使用密钥key创建文本加密器
return new EncryptorFactory(keyProperties.getSalt()).create(keyProperties.getKey());
}
EncryptorFactory
public TextEncryptor create(String data) {
TextEncryptor encryptor;
if (data.contains("RSA PRIVATE KEY")) {
// 密钥key中包含RSA PRIVATE KEY 创建RsaSecretEncryptor(这个是pem文件格式中的Rsa私钥)
encryptor = new RsaSecretEncryptor(data.replaceAll("\\n *", ""));
}
else if (data.startsWith("ssh-rsa") || data.contains("RSA PUBLIC KEY")) {
throw new KeyFormatException();
}
else {
// 返回16进制的Aes加密器
encryptor = Encryptors.text(data, this.salt);
}
return encryptor;
}
DefaultBootstrapContext的延迟加载机制
// type需要获取实例的类型 exceptionSupplier获取不到抛出的异常
@Override
public <T, X extends Throwable> T getOrElseThrow(Class<T> type, Supplier<? extends X> exceptionSupplier) throws X {
synchronized (this.instanceSuppliers) {
// 从注册的instanceSuppliers中获取指定类型的InstanceSupplier
InstanceSupplier<?> instanceSupplier = this.instanceSuppliers.get(type);
if (instanceSupplier == null) {
throw exceptionSupplier.get();
}
return getInstance(type, instanceSupplier);
}
}
@SuppressWarnings("unchecked")
private <T> T getInstance(Class<T> type, InstanceSupplier<?> instanceSupplier) {
// 先从单例池中获取
T instance = (T) this.instances.get(type);
if (instance == null) {
// 不存在在获取对应实例
instance = (T) instanceSupplier.get(this);
if (instanceSupplier.getScope() == Scope.SINGLETON) {
// 单例存放在单例池中
this.instances.put(type, instance);
}
}
// 返回对应实例
return instance;
}
延迟获取是为了在实例依赖的对象还没有初始化时,先注册一个InstanceSupplier。当实例依赖的对象创建完成时,在进行初始化。例如以上KeyProperties就是在Environment解析完成后进行的初始化,放在了BootstrapContext的close事件中,这时依赖的配置环境已经初始化完成。