江苏省建设厅官方网站资质查询百度网盘链接
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
@Overridepublic 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;@Overridepublic 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更多只是暴露出来配置的加密器,供应用程序使用。
源码解析
@Overridepublic 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单例beanTextEncryptorUtils.promote(bootstrapContext, beanFactory);});}
TextEncryptorUtils
public static void register(BootstrapRegistry registry) {// 注册TextEncryptorregistry.registerIfAbsent(TextEncryptor.class, context -> {KeyProperties keyProperties = context.get(KeyProperties.class);// KeyProperties中是否配置了key或者密钥文件locationif (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();});// 注册TextEncryptorBindHandlerregistry.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加密后类容一起Base64return 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获取不到抛出的异常@Overridepublic <T, X extends Throwable> T getOrElseThrow(Class<T> type, Supplier<? extends X> exceptionSupplier) throws X {synchronized (this.instanceSuppliers) {// 从注册的instanceSuppliers中获取指定类型的InstanceSupplierInstanceSupplier<?> 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事件中,这时依赖的配置环境已经初始化完成。