Spring Boot配置文件敏感信息加密

一,背景

Spring Boot应用中的数据库、Redis、Nacos、MQ等的用户名、连接地址、密码在配置文件中一般都是明文存储,如果系统被系统攻破或者配置文件所在的目录读权限被破解,又或者是动态配置文件被窃取,内部人员或者黑客很容易通过配置文件获取到数据库的用户名和密码,进而达到非法连接数据库盗取数据的目的。

本文的目标是对配置文件的敏感信息加密,同时保持对现有应用的最小改动,对应用中的配置文件中的秘文配置项的使用保持和加密前一致,也就是使用配置项不受到任何影响。

二,SpringBoot自动配置原理分析

请添加图片描述

请添加图片描述

2.1 配置文件加载准备

  1. 我们知道SpringApplication构造器加载完Initialzers和Listenter后开始调用run(String…args)方法启动Springboot上下文。

	/**
	 * Run the Spring application, creating and refreshing a new
	 * {@link ApplicationContext}.
	 * @param args the application arguments (usually passed from a Java main method)
	 * @return a running {@link ApplicationContext}
	 */
	public ConfigurableApplicationContext run(String... args) {
		long startTime = System.nanoTime();
		DefaultBootstrapContext bootstrapContext = createBootstrapContext();
		ConfigurableApplicationContext context = null;
		configureHeadlessProperty();
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting(bootstrapContext, this.mainApplicationClass);
		try {
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
			//	配置文件加载入口,它会去执行SpringApplication构造器加载到Lister
			ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
			Banner printedBanner = printBanner(environment);
			context = createApplicationContext();
			context.setApplicationStartup(this.applicationStartup);
			prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
			refreshContext(context);
			afterRefresh(context, applicationArguments);
			Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
			}
			listeners.started(context, timeTakenToStartup);
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
			if (ex instanceof AbandonedRunException) {
				throw ex;
			}
			handleRunFailure(context, ex, listeners);
			throw new IllegalStateException(ex);
		}
		try {
			if (context.isRunning()) {
				Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
				listeners.ready(context, timeTakenToReady);
			}
		}
		catch (Throwable ex) {
			if (ex instanceof AbandonedRunException) {
				throw ex;
			}
			handleRunFailure(context, ex, null);
			throw new IllegalStateException(ex);
		}
		return context;
	}
    • SpringApplication#prepareEnvironment( listeners, applicationArguments), 这个方法是配置文件加载路口,他会执行SpringApplication构造器加载到Listener。这里我们重要关注BootstrapApplicationListener和ConfigFileApplicationListener这两个监听器。
package org.springframework.boot.SpringApplication;
	private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
			DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
		// 给容器创建一个 environment
		ConfigurableEnvironment environment = getOrCreateEnvironment();
		configureEnvironment(environment, applicationArguments.getSourceArgs());
		ConfigurationPropertySources.attach(environment);
		// 执行引入jar包类路径下的META/INF/spring.factories文件到监听器
		listeners.environmentPrepared(bootstrapContext, environment);
		DefaultPropertiesPropertySource.moveToEnd(environment);
		Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
				"Environment prefix cannot be set via properties.");
		// 将加载完成的环境变量信息绑定到Spring IOC容器中
		bindToSpringApplication(environment);
		if (!this.isCustomEnvironment) {
			EnvironmentConverter environmentConverter = new EnvironmentConverter(getClassLoader());
			environment = environmentConverter.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
		}
		ConfigurationPropertySources.attach(environment);
		return environment;
	}
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
    ResolvableType type = eventType != null ? eventType : this.resolveDefaultEventType(event);
    Executor executor = this.getTaskExecutor();
    Iterator var5 = this.getApplicationListeners(event, type).iterator();

    while(var5.hasNext()) {
        ApplicationListener<?> listener = (ApplicationListener)var5.next();
        if (executor != null) {
            executor.execute(() -> {
                this.invokeListener(listener, event);
            });
        } else {
        // 触发BootstrapConfigFileApplicationListener
            this.invokeListener(listener, event);
        }
    }

}
    • SpringApplication#prepareEnvironment()触发执行监听器,优先执行BootstrapApplicationListener监听器,再执行ConfigFileApplicationListener监听器
  • BootstrapApplicationListener:来自SpringCloud。优先级最高,用于启动/建立Springcloud的应用上下文。需要注意的是,到此时Springboot的上下文还未创建完成,因为在创建springboot上下文的时候通过BootstrapApplicationListener去开启了springcloud上下文的创建流程。这个流程“嵌套”特别像是Bean初始化流程:初始化A时,A->B,就必须先去完成Bean B的初始化,再回来继续完成A的初始化。
  • 在建立SpringCloud的应用的时候,使用的也是SpringApplication#run()完成的,所以也会走一整套SpringApplication的生命周期逻辑。这里之前就踩过坑,初始化器、监听器等执行多次,若只需执行一次,需要自行处理。
  • Springcloud和Springboot应用上下文都是使用ConfigFileApplicationListener来完成加载和解析的
Springboot应用上下文读取的是配置文件默认是:application
Springcloud应用上下文读取的外部配置文件名默认是:bootstrap
  • BootstrapApplicationListener 核心代码
	@Override
	public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
   
		// 检查是否开启了SpringCloud
		ConfigurableEnvironment environment = event.getEnvironment();
		if (!bootstrapEnabled(environment) && !useLegacyProcessing(environment)) {
   
			return;
		}
		// don't listen to events in a bootstrap context
		// 如果执行了Springcloud上下文触发的BootStapApplicationListener这个监听器,就不执行这个监听器了 避免重复执行
		if (environment.getPropertySources().contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
   
			return;
		}
		ConfigurableApplicationContext context = null;
		String configName = environment.resolvePlaceholders("${spring.cloud.bootstrap.name:bootstrap}");
		for (ApplicationContextInitializer<?> initializer : event.getSpringApplication().getInitializers()) {
   
			if (initializer instanceof ParentContextApplicationContextInitializer) {
   
				context = findBootstrapContext((ParentContextApplicationContextInitializer) initializer, configName);
			}
		}
		// 如果还未创建SpringCloud上下文实例,则调用bootstrapServiceContext
		if (context == null) {
   
			context = bootstrapServiceContext(environment, event.getSpringApplication(), configName);
			event.getSpringApplication().addListeners(new CloseContextOnFailureApplicationListener(context));
		}

		apply(context, event.getSpringApplication(), environment);
	}
  • BootstrapApplicationListener#bootstrapServiceContext()核心源码如下
private ConfigurableApplicationContext bootstrapServiceContext(ConfigurableEnvironment environment,
			final SpringApplication application, String configName) {
   
		ConfigurableEnvironment bootstrapEnvironment = new AbstractEnvironment() {
   
		};
		MutablePropertySources bootstrapProperties = bootstrapEnvironment.getPropertySources();
		String configLocation = environment.resolvePlaceholders("${spring.cloud.bootstrap.location:}");
		String configAdditionalLocation = environment
				.resolvePlaceholders("${spring.cloud.bootstrap.additional-location:}");
		Map<String, Object> bootstrapMap = new HashMap<>();
		bootstrapMap.put("spring.config.name", configName);
		// if an app (or test) uses spring.main.web-application-type=reactive, bootstrap
		// will fail
		// force the environment to use none, because if though it is set below in the
		// builder
		// the environment overrides it
		bootstrapMap.put("spring.main.web-application-type", "none");
		if (StringUtils.hasText(configLocation)) {
   
			bootstrapMap.put("spring.config.location", configLocation);
		}
		if (StringUtils.hasText(configAdditionalLocation)) {
   
			bootstrapMap.put("spring.config.additional-location", configAdditionalLocation);
		}
		bootstrapProperties.addFirst(new MapPropertySource(BOOTSTRAP_PROPERTY_SOURCE_NAME, bootstrapMap));
		for (PropertySource<?> source : environment.getPropertySources()) {
   
			if (source instanceof StubPropertySource) {
   
				continue;
			}
			bootstrapProperties.addLast(source);
		}
		// TODO: is it possible or sensible to share a ResourceLoader?
		// 通过SpringApplicationBuilder构建一个SpringCloud的上下文实例
		SpringApplicationBuilder builder = new SpringApplicationBuilder().profiles(environment.getActiveProfiles())
				.bannerMode(Mode.OFF).environment(bootstrapEnvironment)
				// Don't use the default properties in this builder
				.registerShutdownHook(false).logStartupInfo(false).web(WebApplicationType.NONE);
		final SpringApplication builderApplication = builder.application();
		if (builderApplication.getMainApplicationClass() == null) {
   
			// gh_425:
			// SpringApplication cannot deduce the MainApplicationClass here
			// if it is booted from SpringBootServletInitializer due to the
			// absense of the "main" method in stackTraces.
			// But luckily this method's second parameter "application" here
			// carries the real MainApplicationClass which has been explicitly
			// set by SpringBootServletInitializer itself already.
			builder.main(application.
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值