一、说明
- 该starter在被项目依赖后会在Springboot启动之初自动完成加密属性的解密并将解密后的内容替换到springboot配置容器中。
- 加密的属性在以固定的格式开头,例如:key = encryptionencryptionencryptionabcdefg
- 可以在配置文件中指定自定义的解密类的全限定名,调用格式如下:decrypt.class = com.zxl.decrypt.noOpDecrypt
- 使用方式:直接依赖启动即可
?
二、starter创建过程
1、在IDEA中建立一个空的工程,在工程中分别创建两个模块decrypt-springboot-starter和decrypt-springboot-autoconfiguration,decrypt-springboot-starter模块没有实际功能,并依赖于decrypt-springboot-autoconfiguration模块。
2、构建decrypt-springboot-autoconfiguration模块,该模块实现了starter的核心功能,为了构建stater,在该模块依赖springboot官方提供的自动配置jar包,注意为了防止依赖冲突,将jar包的作用域设置为私有。
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-autoconfigure</artifactId><version>2.3.0.RELEASE</version><scope>provided</scope>
</dependency>
3、由于该starter的功能是在其它starter加载之前完成加密属性的替换,因此可以使用一个实现了环境后置处理器的类MyEnvironmentPostProcessor来实现,在springboot完成系统环境的加载之后会根据spring.factory文件的指引调用该类的postProcessEnvironment方法,步骤如下:
- 在META-INF目录下创建spring.factory文件,并在文件中注册EnvironmentPostProcessor:
org.springframework.boot.env.EnvironmentPostProcessor=\
com.taobao.starter.MyEnvironmentPostProcessor
- 创建MyEnvironmentPostProcessor类并实现EnvironmentPostProcessor接口
4、在MyEnvironmentPostProcessor中实现后置处理器方法,postProcessEnvironment的处理思路如下:
- 获得来自于applicationConfig的键值对属性资源
- 判断用户是否人为指定了解密实现类,如果有通过发射失利话解密类
- 如果根据用户的配置没有获取到解析类对象,则采用原始的解析类
- 遍历application文件中所有的键值对
- 检查键值对是否为加密数据,如果是的话解密并把解密后的键值对保存到map中
- 将map中所有的键值对组成一个属性源并加载到上下文环境,完成覆盖
程序如下:
public class MyEnvironmentPostProcessor implements EnvironmentPostProcessor {
private static final String APPLICATION_SOURCE_NAME = "applicationConfig: [classpath:/application.properties]";private static final String SECRET = "$encryption$";private static final String DECRYPTCLASS = "decrypt.class";//解密类实例private Decrypt decryptClass;public void postProcessEnvironment(ConfigurableEnvironment environment,SpringApplication application) {
//1、获得来自于applicationConfig的键值对属性资源MapPropertySource mapPropertySource =(MapPropertySource) environment.getPropertySources().get(APPLICATION_SOURCE_NAME);if(mapPropertySource==null) return;//2、判断用户是否人为指定了解密实现类,如果有通过发射失利话解密类String decryptClassName = (String)mapPropertySource.getProperty(DECRYPTCLASS);if(null!=decryptClassName){
Class<?> clazz = null;try {
clazz = this.getClass().getClassLoader().loadClass(decryptClassName);//如果该类不是接口并且实现了Decrypt的类,才实例化创建if(!clazz.isInterface()||Decrypt.class.isAssignableFrom(clazz))decryptClass = (Decrypt) clazz.newInstance();elseSystem.out.println("自定义解码类失败");} catch (Exception e) {
System.out.println("自定义解码类失败");}}//3、如果根据用户的配置没有获取到解析类对象,则采用原始的解析类if(decryptClass==null) decryptClass = new fixDecrypt();//4、遍历application文件中所有的键值对String[] propertyNames = mapPropertySource.getPropertyNames();HashMap<String, Object> map = new HashMap(1);for (String s : propertyNames) {
5、检查键值对是否为加密数据,如果是的话解密并把解密后的键值对保存到map中String nVal = check((String)mapPropertySource.getProperty(s));if(null!=nVal) {
map.put(s, decryptClass.decypt(nVal));System.out.println(s+":" +SECRET+nVal+"==>"+decryptClass.decypt(nVal));}}6、将map中所有的键值对组成一个属性源并加载到上下文环境,完成覆盖PropertySource source = new MapPropertySource("decryptSource", map);environment.getPropertySources().addFirst(source);}/*** 判断value是否以SECRET开头* @param value* @return如果是,返回之后的字符串,如果不是,返回空*/private String check(String value){
if(value.length()<SECRET.length()) return null;if(value.substring(0,SECRET.length()).equals(SECRET)) return value.substring(SECRET.length(),value.length());return null;}
}
三、使用方式
1、工程中依赖该starter
<dependency><groupId>com.taobao.zxl</groupId><artifactId>decrypt-springboot-starter</artifactId><version>1.0-SNAPSHOT</version>
</dependency>
2、直接启动即可,如果要自定义解析类,则实现Decrypt接口再指定全限定名即可
public class noOpDecrypt implements Decrypt {
@Overridepublic String decypt(String s) {
return s;}
}