当前位置:   article > 正文

jasypt集成spring-boot原理解析_jasypt-spring-boot-starter

jasypt-spring-boot-starter

背景

每个应用都有很多配置项,有些配置项对外非常敏感,例如数据库连接密码、私钥等。使用明文存在泄露的风险,生产环境要配合加密算法。jasypt是一个方便、流行的加密工具,支持PBE、AEC和对称加密。它与spring-boot的集成度很高,可以方便的为spring-boot属性进行加解密。

使用方式

要使用jayspt要引入pom文件:

  1. <dependency>
  2. <groupId>org.jasypt</groupId>
  3. <artifactId>jasypt</artifactId>
  4. <version>1.9.3</version>
  5. </dependency>

以PBE加密为例,要先初始化Encryptor 对象,然后对“abc”进行加解密。

  1. public class EncryptionDemo {
  2. public static void main(String[] args) {
  3. StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor();
  4. SimpleStringPBEConfig config = new SimpleStringPBEConfig();
  5. config.setPassword("123");
  6. encryptor.setConfig(config);
  7. String rawMsg = "abc";
  8. String encryptedMsg = encryptor.encrypt(rawMsg);
  9. System.out.println(rawMsg + " = " + encryptedMsg);
  10. // abc = tgjLqtpTOf7z6IC+xE9r+w==
  11. String decryptedMsg = encryptor.decrypt(encryptedMsg);
  12. System.out.println(encryptedMsg + " = " + decryptedMsg);
  13. // tgjLqtpTOf7z6IC+xE9r+w== = abc
  14. }
  15. }

可以看到“abc”加密后结果是“tgjLqtpTOf7z6IC+xE9r+w==”,解密后还是“abc”。并且同样的“abc”参数,每次加密的结果都不一样,但解密的结果是一样的。

集成spring-boot

加解密功能已经有了,接下来怎么集成到spring-boot上,将spring-boot里的配置项自动解密?

由于spring-boot的配置项是放在PropertySource里,jasypt通过生成PropertySource的代理对象。后面从PropertySource获取属性时,先通过jasypt先解密,再返回解密后的值。

下面再看一下jasypt具体是怎么实现的代理,先引入对应的pom文件:

  1. <dependency>
  2. <groupId>com.github.ulisesbocchio</groupId>
  3. <artifactId>jasypt-spring-boot-starter</artifactId>
  4. <version>3.0.5</version>
  5. </dependency>

生成代理对象

这个starter通过SPI的方式,自动加载了EnableEncryptablePropertiesConfiguration配置类,这个类里注册了jasypt自己的BeanFactoryPostProcessor 。它里面的功能就是拿到ConfigurableEnvironment 里的MutablePropertySourcesMutablePropertySources 里包含所有的PropertySource。把PropertySource包装成自己的EncryptablePropertySource ,然后替换掉原来的PropertySource。

  1. // com.ulisesbocchio.jasyptspringboot.EncryptablePropertySourceConverter
  2. public void convertPropertySources(MutablePropertySources propSources) {
  3. propSources.stream()
  4. .filter(ps -> !(ps instanceof EncryptablePropertySource))
  5. // makeEncryptable方法会将PropertySource替换成EncryptablePropertySource的子类
  6. .map(this::makeEncryptable)
  7. .collect(toList())
  8. .forEach(ps -> propSources.replace(ps.getName(), ps));
  9. }
  10. // 这里还分两种代理方式,一种是Proxy,一个是Wrapper
  11. private <T> PropertySource<T> convertPropertySource(PropertySource<T> propertySource) {
  12. return interceptionMode == InterceptionMode.PROXY
  13. ? proxyPropertySource(propertySource) : instantiatePropertySource(propertySource);
  14. }
  15. // Proxy会通过Spring的AOP生成代理对象
  16. private <T> PropertySource<T> proxyPropertySource(PropertySource<T> propertySource) {
  17. //can't be proxied with CGLib because of methods being final. So fallback to wrapper for Command Line Arguments only.
  18. if (CommandLinePropertySource.class.isAssignableFrom(propertySource.getClass())
  19. // Other PropertySource classes like org.springframework.boot.env.OriginTrackedMapPropertySource
  20. // are final classes as well
  21. || Modifier.isFinal(propertySource.getClass().getModifiers())) {
  22. return instantiatePropertySource(propertySource);
  23. }
  24. ProxyFactory proxyFactory = new ProxyFactory();
  25. proxyFactory.setTargetClass(propertySource.getClass());
  26. proxyFactory.setProxyTargetClass(true);
  27. proxyFactory.addInterface(EncryptablePropertySource.class);
  28. proxyFactory.setTarget(propertySource);
  29. proxyFactory.addAdvice(new EncryptablePropertySourceMethodInterceptor<>(propertySource, propertyResolver, propertyFilter));
  30. return (PropertySource<T>) proxyFactory.getProxy();
  31. }
  32. // Wrapper会跟进原始的PropertySource类型,生成对应的包装类
  33. private <T> PropertySource<T> instantiatePropertySource(PropertySource<T> propertySource) {
  34. PropertySource<T> encryptablePropertySource;
  35. if (needsProxyAnyway(propertySource)) {
  36. encryptablePropertySource = proxyPropertySource(propertySource);
  37. } else if (propertySource instanceof SystemEnvironmentPropertySource) {
  38. encryptablePropertySource = (PropertySource<T>) new EncryptableSystemEnvironmentPropertySourceWrapper((SystemEnvironmentPropertySource) propertySource, propertyResolver, propertyFilter);
  39. } else if (propertySource instanceof MapPropertySource) {
  40. encryptablePropertySource = (PropertySource<T>) new EncryptableMapPropertySourceWrapper((MapPropertySource) propertySource, propertyResolver, propertyFilter);
  41. } else if (propertySource instanceof EnumerablePropertySource) {
  42. encryptablePropertySource = new EncryptableEnumerablePropertySourceWrapper<>((EnumerablePropertySource) propertySource, propertyResolver, propertyFilter);
  43. } else {
  44. encryptablePropertySource = new EncryptablePropertySourceWrapper<>(propertySource, propertyResolver, propertyFilter);
  45. }
  46. return encryptablePropertySource;
  47. }

后面从MutablePropertySources 获取属性的时候,实际上调用的是EncryptablePropertySourceObject getProperty(String name) 方法。

解析属性流程

以applicaiton.properties文件里的配置项为例,它是OriginTrackedMapPropertySource类型,属于MapPropertySource 的子类,会被jasypt包装成EncryptableMapPropertySourceWrapper

想在获取OriginTrackedMapPropertySource 里的属性,会先调用EncryptableMapPropertySourceWrappergetProperty(String name) 方法。

EncryptableMapPropertySourceWrapper 会将请求转交给代理对象CachingDelegateEncryptablePropertySource 。因为CachingDelegateEncryptablePropertySource 会将解析的结果放到Map<String, CachedValue> cache 缓存对象里,避免重复解析。

然后,CachingDelegateEncryptablePropertySource 会将加密内容交给DefaultLazyPropertyResolver 解密配置项。从名字就可以看出,DefaultLazyPropertyResolver 是为了延迟初始化,实际上它包装了DefaultPropertyResolver ,这是默认jasypt的解析器。

DefaultPropertyResolver 先加加密内容执行spring的占位符解析,例如解析${my.password},得到“ENC(tgjLqtpTOf7z6IC+xE9r+w==)”,再对”ENC(tgjLqtpTOf7z6IC+xE9r+w==)”进行解密。

DefaultPropertyResolver 内部调用DefaultLazyEncryptor ,通过延迟加载的方式得到默认的加密机类型。这时会根据上下文配置,选择是使用PBE、AEC还是对称加密。

  1. // com.ulisesbocchio.jasyptspringboot.configuration.StringEncryptorBuilder
  2. public StringEncryptor build() {
  3. // 包含jasypt.encryptor.password就使用PBE
  4. if (isPBEConfig()) {
  5. return createPBEDefault();
  6. // 包含jasypt.encryptor.privateKeyString相关的配置就使用非对称加密
  7. } else if (isAsymmetricConfig()) {
  8. return createAsymmetricDefault();
  9. // 包含jasypt.encryptor.gcmSecretKeyString相关配置就使用GCM加密
  10. } else if (isGCMConfig()) {
  11. return createGCMDefault();
  12. } else {
  13. throw new IllegalStateException("either '" + propertyPrefix + ".password', one of ['" + propertyPrefix + ".private-key-string', '" + propertyPrefix + ".private-key-location'] for asymmetric encryption, or one of ['" + propertyPrefix + ".gcm-secret-key-string', '" + propertyPrefix + ".gcm-secret-key-location', '" + propertyPrefix + ".gcm-secret-key-password'] for AES/GCM encryption must be provided for Password-based or Asymmetric encryption");
  14. }
  15. }

以PBE为例,这里方法的就是PooledPBEStringEncryptor 对象。它内部有多个StandardPBEStringEncryptor 组成一个池子,可以提升加密性能。最后会按顺序获得一个StandardPBEStringEncryptor ,调用decrypt(final String encryptedMessage) 方法进行解密。

总结

jasypt通过BeanFactoryPostProcessor 代理了所有的PropertySource,在获取属性之前,对属性进行解密。过程中涉及到了几个对象:

  • EncryptablePropertySource 是PropertySource的包装类
  • CachingDelegateEncryptablePropertySource 缓存了解密结果
  • EncryptableMapPropertySourceWrapperMapPropertySource 的包装类
  • DefaultPropertyResolver 先解析Spring的占位符
  • DefaultLazyPropertyResolver 实现了延迟加载DefaultPropertyResolver 对象
  • StandardPBEStringEncryptor 是PBE加密机
  • PooledPBEStringEncryptor 通过池化StandardPBEStringEncryptor 提升并发度

引用

jasypt的github:https://github.com/ulisesbocchio/jasypt-spring-boot?tab=readme-ov-file

本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号