当前位置: 首页 > news >正文

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事件中,这时依赖的配置环境已经初始化完成。在这里插入图片描述

相关文章:

  • [算法笔记]cin和getline的并用、如何区分两个数据对、C++中std::tuple类
  • uniapp版本加密货币行情应用
  • Unity DOTS从入门到精通之EntityCommandBufferSystem
  • C#模拟鼠标点击,模拟鼠标双击,模拟鼠标恒定速度移动,可以看到轨迹
  • 【vitepress】如何搭建并部署自己的博客网站
  • sqlserver中的锁模式 | SQL SERVER如何开启MVCC(使用row-versioning)【启用行版本控制减少锁争用】
  • 基于单片机的智慧农业大棚系统(论文+源码)
  • mysql(community版)压缩包安装教程
  • 【计算机网络】确认家庭网络是千兆/百兆带宽并排查问题
  • 解决电脑问题(5)——鼠标问题
  • Android SharedPreferences 工具类封装:高效、简洁、易用
  • MySql数据库增删改查常用语句命令-MySQL步骤详解教程
  • Docker 的基本概念和优势,以及在应用程序开发中的实际应用
  • (十七) Nginx解析:架构设计、负载均衡实战与常见面试问题
  • windows环境下安装部署dify+本地知识库+线上模型
  • linux安装reids
  • 探索在直播中的面部吸引力预测新的基准和多模态方法
  • Git基础之分支
  • 观看文艺汇演问题
  • YC 孵化项目 Pinch:实时语音翻译视频会议平台;Mistral OCR:能处理多语言多模态复杂文档丨日报
  • 社恐也能嗨起来,《孤独摇滚》千人观影齐舞荧光棒
  • 著名蒙古族音乐学者马•斯尔古愣逝世,享年86岁
  • 5天完成1000多万元交易额,“一张手机膜”畅销海内外的启示
  • 国家主席习近平会见斯洛伐克总理菲佐
  • 古埃及展进入百天倒计时,闭幕前168小时不闭馆
  • 马克思主义理论研究教学名师系列访谈|董雅华:让学生感知马克思主义理论存在于社会生活中