前言
- 看了MyBatisPlus的数据安全保护,对它的实现原理产生好奇,它是怎么做到的呢?后来又参考网上多篇优秀文章,作此笔记
一、介绍
- SpringBoot的一个核心组件EnvironmentPostProcessor,叫做 环境后置处理器
- 它是一个接口,我们可以实现它,它可以在创建应用程序上下文之前(项目启动之前),增、删、改 配置文件信息、环境变量、系统属性
二、应用场景
1、配置文件敏感信息解密
- 配置文件中敏感信息写入的是加密的信息,程序启动时需要解密,框架才能使用
1)配置文件
# 使用加密后的密文,使用统一的加密前缀mpw:
username: mpw:e31oCntywsc1nUkjU+wiGg==
2)代码实现
/**
* 解密配置文件
* 参考MyBatisPlus的数据安全保护
*
* @author kimi
* @date 2022/11/24
*/
public class ConfigDecryptProcessor implements EnvironmentPostProcessor{
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment,SpringApplication application) {
/**
* 1、环境变量
*/
Map<String, Object> systemEnvironment=environment.getSystemEnvironment();
/**
* 2、系统属性(=System.getProperties())
*/
Map<String,Object> systemProperties=environment.getSystemProperties();
/**
* 3、配置文件
*/
//单个属性
Integer port=environment.getProperty("server.port",Integer.class);
//激活的配置文件名称(=spring-profiles-active)
String[] activeProfiles=environment.getActiveProfiles();
//添加配置文件
//environment.addActiveProfile();
//设置配置文件
//environment.setActiveProfiles();
/**
* 4、所有的配置源
* 包含 环境变量、系统属性、配置文件 . . .
*/
MutablePropertySources mutablePropertySources=environment.getPropertySources();
/**
* 1)获取密钥
*/
String mpwKey=null;
for (PropertySource<?> ps:mutablePropertySources) {
/**
* 配置源名称
* configurationProperties
* servletConfigInitParams
* servletContextInitParams
* systemProperties
* systemEnvironment
* random
* applicationConfig: [classpath:/config/application-druid.yml]
* applicationConfig: [classpath:/config/application.yml]
*/
String psName=ps.getName();
/**
* 配置源PropertySource是一个抽象类,它的实现类有:
* OriginTrackedMapPropertySource:配置文件(application*.yml/properties)
* RandomValuePropertySource:random
* ServletConfigPropertySource servletConfigInitParams
* ServletContextPropertySource servletContextInitParams
* CommandLinePropertySource、SimpleCommandLinePropertySource:以输入命令行参数作为配置源:启动命令、idea的Program arguments
*
*/
//我们在启动命令中指定了密钥:--mpw.key=密钥,所以使用SimpleCommandLinePropertySource实现类
if (ps instanceof SimpleCommandLinePropertySource) {
SimpleCommandLinePropertySource source=(SimpleCommandLinePropertySource) ps;
mpwKey=source.getProperty("mpw.key");
}
}
//密钥为空则无需解密
System.err.println("mpwKey: "+mpwKey);
if(mpwKey==null || mpwKey.isEmpty()){
return;
}
/**
* 2)解密配置文件
*/
//解密配置文件中加密的信息,放入一个Map
Map<String,Object> map=new HashMap<>();
for (PropertySource<?> ps:mutablePropertySources) {
//加密信息在配置文件中,所以使用OriginTrackedMapPropertySource实现类
if (ps instanceof OriginTrackedMapPropertySource) {
OriginTrackedMapPropertySource source=(OriginTrackedMapPropertySource) ps;
//遍历所有的配置信息
for (String name:source.getPropertyNames()) {
Object value=source.getProperty(name);
if (value instanceof String) {
String str=(String) value;
//包含加密前缀,截取去掉统一的加密前缀后的信息
if(str.startsWith("mpw:")){
map.put(name,解密方法(str.substring(5)));
}
}
}
}
}
System.err.println("解密后的配置: "+map.toString());
//解密的Map有值,则添加到配置信息最前面,这样才会覆盖后面加密的配置信息(也就是加密的配置不生效,解密的配置生效)
if (!map.isEmpty()) {
mutablePropertySources.addFirst(new MapPropertySource("custom-encrypt", map));
}
}
}
3)驱动
- springboot启动的时候,不会去主动读取我们自定义的类的,所以我们还需要利用SpringBoot的SPI机制,来加载我们的环境后置处理器
- 在resources下创建
META-INF/spring.factories
,指定实现类的全限定名称(多个逗号分隔)org.springframework.boot.env.EnvironmentPostProcessor=com.race.framework.init.ConfigDecryptProcessor
- key就是EnvironmentPostProcessor类的全限定名