写作目的
数据安全这块还是挺严重的,尤其是自己专注于业务开发,不能总停留在一个地方,还要关注其他的一些问题,比如数据安全。
配置脱敏
实现配置的脱敏我使用了Java的一个加解密工具Jasypt。该工具支持对称加密和非对称加密。
首先通过简单的demo配置进行配置和测试。
1、首先引入jasypt-spring-boot-starter
<!--配置文件加密-->
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
2、接下来就是application的配置,如下面代码所示。其中password是密钥,即类似于MD5盐值加密的盐;prefix和suffix*为判断要解密的规则条件或正则表达式
jasypt:
encryptor:
password: demo # 秘钥
property:
prefix: "abc[" #前缀
suffix: "]" #后缀
3、配置好后我们就需要在配置文件中配置加密后的数据了,如下面的数据库密码
spring:
datasource:
url: jdbc:mysql://localhost:3306/demo?characterEncoding=utf8&useUnicode=true&useSSL=false&serverTimezone=GMT%2B8
username: root
password: abc[B6jeXwl1AotiulW1vfsKmQ==] ### 前缀和后缀包围着密文
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
那么这串密文是怎么来的呢?
System.out.println("--------------加密----------------");
StandardPBEStringEncryptor standardPBEStringEncryptor = new StandardPBEStringEncryptor();
// 秘钥
standardPBEStringEncryptor.setPassword("demo");
// 明文进行加密
String code = standardPBEStringEncryptor.encrypt("123456");
System.out.println(code);
System.out.println("--------------解密----------------");
// 解密
String decrypt = standardPBEStringEncryptor.decrypt(code);
System.out.println(decrypt);
结果为
4、接下来就是启动项目了。
整个配置文件脱敏的配置就算完成了。其实就是两步。配置jasypt的密钥、前缀和后缀等信息;获取加密后的数据(如本文中的数据库密码信息)
脱敏原理
添加BeanFactoryPostProcessor
既然是以springboot方式集成,那么就先从jasypt-spring-boot-starter源码开始入手。该starter中会有一个spring.factories文件,文件中会配置自动装配的类,即JasyptSpringBootAutoConfiguration
该类又通过Import注解引入了EnableEncryptablePropertiesConfiguration。
其中EnableEncryptablePropertiesConfiguration主要向ioc容器中注入了一个EnableEncryptablePropertiesBeanFactoryPostProcessor,其中一个参数为environment,如下图所示,我们只要把这个类搞明白就理解核心了。
该BeanFactoryPostProcessor的目的
那么EnableEncryptablePropertiesBeanFactoryPostProcessor是什么呢?那就要看他实现的接口,该类实现了BeanFactoryPostProcessor接口,那么我们有理由去看postProcessBeanFactory方法(Spring的生命周期知识点)。
在postProcessBeanFactory方法里,该方法的主要逻辑为获取environment的propSources并进行convert转换,如下图所示。
propSources是什么?
propSources其实是环境变量文件或者配置文件的集合。如下图所示。我们直接下标为6的元素里的数据,其实可以发现下标为6的元素对应的就是我们的application.yml。
其实EnableEncryptablePropertiesBeanFactoryPostProcessor获取上述的环境变量文件或者配置文件的数据也可以理解,毕竟你需要对里面的数据进行加密和解密,你不拿到数据怎么加密和解密呢?
convert动作的逻辑是什么?
convert的代码如下图所示,对于propSources里的每一个元素ps,都通过makeEncryptable方法转换为一个新的对象,并替换掉原来的ps。
那么makeEncryptable方法是怎么替换的?一直跟上面的convert最后到了下图中的方法。其实就是把propertySource对象进行包装Wrapper,然后替换掉原来的propertySource对象。
当我们想获取某个配置文件中的kv时,再调用getProperty时其实已经走EncryptablePropertySourceWrapper的getProperty方法了(因为上面被替换了)。
获取配置文件的kv进行过滤和解密
当然经过各种实现类和实现类里依赖的其他接口的实现类,最后getProperty的方法会到下面的方法里,如下面代码所示,其实就是先在原始source获取数据,然后判断是否需要解密,需要的话就直接把解密后的数据返回,完美了。
#EncryptablePropertySource
default Object getProperty(EncryptablePropertyResolver resolver, EncryptablePropertyFilter filter, PropertySource<T> source, String name) {
Object value = source.getProperty(name);
if (filter.shouldInclude(source, name) && value instanceof String) {
String stringValue = String.valueOf(value);
return resolver.resolvePropertyValue(stringValue);
}
return value;
}
总结
注册一个BeanFactoryPostProcessor
在postProcessBeanFactory方法里对propSources进行包装为propSourcesWrapper
在获取配置文件时对propSourcesWrapper进行获取数据,当符合解密规则时进行解密
参考
Springboot 配置文件、隐私数据脱敏的最佳实践(原理+源码)