实践——使用Jasypt加密SpringBoot配置文件加密springboot配置文件

本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

原文链接:blog.ouyangsihai.cn >> 实践——使用Jasypt加密SpringBoot配置文件加密springboot配置文件

点击上方“Java知音”,选择“置顶公众号”

技术文章第一时间送达!

小试牛刀

1.构建一个springboot项目,并且引入jasypt依赖


dependency
        groupIdcom.github.ulisesbocchio/groupId
        artifactIdjasypt-spring-boot-starter/artifactId
        version3.0.2/version
/dependency

2.编写一个单元测试,用于获取加密后的账号密码

StringEncryptor是jasypt-spring-boot-starter自动配置的加密工具,加密算法我们选择PBEWithHmacSHA512AndAES_128,password为123abc


jasypt.encryptor.password=123abc
jasypt.encryptor.algorithm=PBEWithHmacSHA512AndAES_128

@SpringBootTest
class SpringbootPropertiesEncApplicationTests {

    @Autowired
    private StringEncryptor stringEncryptor;

    @Test
    void contextLoads() {
        String sunshujie = stringEncryptor.encrypt("sunshujie");
        String qwerty1234 = stringEncryptor.encrypt("qwerty1234");
        System.out.println(sunshujie);
        System.out.println(qwerty1234);
    }

}

3.在application.properties中配置加密后的账号密码


jasypt.encryptor.password=123abc
jasypt.encryptor.algorithm=PBEWithHmacSHA512AndAES_128
username=ENC(pXDnpH3GdMDBHdxraKyAt7IKCeX8mVlM9A9PeI9Ow2VUoBHRESQ5m8qhrbp45vH+)
password=ENC(qD55H3EKYpxp9cGBqpOfR2pqD/AgqT+IyClWKdW80MkHx5jXEViaJTAx6Es4/ZJt)

4.观察在程序中是否能够拿到解密后的账号密码


@SpringBootApplication
public class SpringbootPropertiesEncApplication implements CommandLineRunner {
    private static final Logger logger = LoggerFactory.getLogger(SpringbootPropertiesEncApplication.class);
    public static void main(String[] args) {
        SpringApplication.run(SpringbootPropertiesEncApplication.class, args);
    }

    @Value("${password}")
    private String password;
    @Value("${username}")
    private String username;

    @Override
    public void run(String... args) throws Exception {
        logger.info("username: {} , password: {} ", username, password);
    }
}

原理解析

加密原理

首先看jasypt相关的配置,分别是password和加密算法


jasypt.encryptor.password=123abc
jasypt.encryptor.algorithm=PBEWithHmacSHA512AndAES_128

PBEWithHmacSHA512AndAES_128是此次我们选用的加密算法.

123abc是PBEWithHmacSHA512AndAES_128加密过程中用的加密密码.

PBE是基于密码的加密算法,密码和秘钥相比有什么好处呢?好处就是好记…

PBE加密流程如下

  • 密码加盐
  • 密码加盐结果做摘要获取秘钥
  • 用秘钥对称加密原文,然后和盐拼在一起得到密文
  • 密码加盐结果做摘要获取秘钥

    PBE解密流程如下

  • 从密文获取盐
  • 密码+盐摘要获取秘钥
  • 密文通过秘钥解密获取原文
  • 密码+盐摘要获取秘钥

    再来看PBEWithHmacSHA512AndAES_128,名字就是加密过程中用的具体算法

  • PBE是指用的是PBE加密算法
  • HmacSHA512是指摘要算法,用于获取秘钥
  • AES_128是对称加密算法
  • HmacSHA512是指摘要算法,用于获取秘钥

    jasypt-spring-boot-starter原理

    先从spring.factories文件入手查看自动配置类

    
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.ulisesbocchio.jasyptspringboot.JasyptSpringBootAutoConfiguration
    

    JasyptSpringBootAutoConfiguration配置仅仅使用@Import注解引入另一个配置类 EnableEncryptablePropertiesConfiguration.

    
    @Configuration
    @Import({EnableEncryptablePropertiesConfiguration.class})
    public class JasyptSpringBootAutoConfiguration {
        public JasyptSpringBootAutoConfiguration() {
        }
    }
    

    从配置类 EnableEncryptablePropertiesConfiguration可以看到有两个操作

    1.@Import了 EncryptablePropertyResolverConfiguration.class, CachingConfiguration.class

    2.注册了一个BeanFactoryPostProcessor -  EnableEncryptablePropertiesBeanFactoryPostProcessor

    
    @Configuration
    @Import({EncryptablePropertyResolverConfiguration.class, CachingConfiguration.class})
    public class EnableEncryptablePropertiesConfiguration {
        private static final Logger log = LoggerFactory.getLogger(EnableEncryptablePropertiesConfiguration.class);
    
        public EnableEncryptablePropertiesConfiguration() {
        }
    
        @Bean
        public static EnableEncryptablePropertiesBeanFactoryPostProcessor enableEncryptablePropertySourcesPostProcessor(ConfigurableEnvironment environment) {
            boolean proxyPropertySources = (Boolean)environment.getProperty("jasypt.encryptor.proxy-property-sources", Boolean.TYPE, false);
            InterceptionMode interceptionMode = proxyPropertySources ? InterceptionMode.PROXY : InterceptionMode.WRAPPER;
            return new EnableEncryptablePropertiesBeanFactoryPostProcessor(environment, interceptionMode);
        }
    }
    

    先看 EncryptablePropertyResolverConfiguration.class

    lazyEncryptablePropertyDetector这里有配置文件中**ENC()**写法的出处.从名称来看是用来找到哪些配置需要解密.

    从代码来看,不一定非得用ENC()把密文包起来, 也可以通过配置来指定其他前缀和后缀

    
    jasypt.encryptor.property.prefix
    jasypt.encryptor.property.suffix
    
    
        @Bean(
            name = {"lazyEncryptablePropertyDetector"}
        )
        public EncryptablePropertyDetector encryptablePropertyDetector(EncryptablePropertyResolverConfiguration.EnvCopy envCopy, BeanFactory bf) {
            String prefix = envCopy.get().resolveRequiredPlaceholders("${jasypt.encryptor.property.prefix:ENC(}");
            String suffix = envCopy.get().resolveRequiredPlaceholders("${jasypt.encryptor.property.suffix:)}");
            String customDetectorBeanName = envCopy.get().resolveRequiredPlaceholders(DETECTOR_BEAN_PLACEHOLDER);
            boolean isCustom = envCopy.get().containsProperty("jasypt.encryptor.property.detector-bean");
            return new DefaultLazyPropertyDetector(prefix, suffix, customDetectorBeanName, isCustom, bf);
        }
    

    另外还配置了很多bean,先记住这两个重要的bean.带着疑问往后看.

  • `lazyEncryptablePropertyResolver` 加密属性解析器
  • `lazyEncryptablePropertyFilter` 加密属性过滤器
  • 
        @Bean(
            name = {"lazyEncryptablePropertyResolver"}
        )
        public EncryptablePropertyResolver encryptablePropertyResolver(@Qualifier("lazyEncryptablePropertyDetector") EncryptablePropertyDetector propertyDetector, @Qualifier("lazyJasyptStringEncryptor") StringEncryptor encryptor, BeanFactory bf, EncryptablePropertyResolverConfiguration.EnvCopy envCopy, ConfigurableEnvironment environment) {
            String customResolverBeanName = envCopy.get().resolveRequiredPlaceholders(RESOLVER_BEAN_PLACEHOLDER);
            boolean isCustom = envCopy.get().containsProperty("jasypt.encryptor.property.resolver-bean");
            return new DefaultLazyPropertyResolver(propertyDetector, encryptor, customResolverBeanName, isCustom, bf, environment);
        }
    
        @Bean(
            name = {"lazyEncryptablePropertyFilter"}
        )
        public EncryptablePropertyFilter encryptablePropertyFilter(EncryptablePropertyResolverConfiguration.EnvCopy envCopy, ConfigurableBeanFactory bf, @Qualifier("configPropsSingleton") SingletonJasyptEncryptorConfigurationProperties configProps) {
            String customFilterBeanName = envCopy.get().resolveRequiredPlaceholders(FILTER_BEAN_PLACEHOLDER);
            boolean isCustom = envCopy.get().containsProperty("jasypt.encryptor.property.filter-bean");
            FilterConfigurationProperties filterConfig = ((JasyptEncryptorConfigurationProperties)configProps.get()).getProperty().getFilter();
            return new DefaultLazyPropertyFilter(filterConfig.getIncludeSources(), filterConfig.getExcludeSources(), filterConfig.getIncludeNames(), filterConfig.getExcludeNames(), customFilterBeanName, isCustom, bf);
        }
    

    再看 EnableEncryptablePropertiesBeanFactoryPostProcessor这个类

  • 是一个`BeanFactoryPostProcessor`
  • 实现了Ordered,是最低优先级,会在其他`BeanFactoryPostProcessor`执行之后再执行
  • `postProcessBeanFactory`方法中获取了上面提到的两个重要的bean, `lazyEncryptablePropertyResolver lazyEncryptablePropertyFilter`
  • 从`environment`中获取了`PropertySources`
  • 调用工具类进行转换`PropertySources`, 也就是把密文转换为原文

  • 实现了Ordered,是最低优先级,会在其他 BeanFactoryPostProcessor执行之后再执行

    environment中获取了 PropertySources

    
    public class EnableEncryptablePropertiesBeanFactoryPostProcessor implements BeanFactoryPostProcessor, Ordered {    
        // ignore some code
        public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
            LOG.info("Post-processing PropertySource instances");
            EncryptablePropertyResolver propertyResolver = (EncryptablePropertyResolver)beanFactory.getBean("lazyEncryptablePropertyResolver", EncryptablePropertyResolver.class);
            EncryptablePropertyFilter propertyFilter = (EncryptablePropertyFilter)beanFactory.getBean("lazyEncryptablePropertyFilter", EncryptablePropertyFilter.class);
            MutablePropertySources propSources = this.environment.getPropertySources();
            EncryptablePropertySourceConverter.convertPropertySources(this.interceptionMode, propertyResolver, propertyFilter, propSources);
        }
    
        public int getOrder() {
            return 2147483547;
        }
    }
    

    再看工具类 EncryptablePropertySourceConverter

    1.过滤所有已经是 EncryptablePropertySource PropertySource

    2.转换为 EncryptablePropertySource

    3.用 EncryptablePropertySource PropertySources中替换原 PropertySource

    
        public static void convertPropertySources(InterceptionMode interceptionMode, EncryptablePropertyResolver propertyResolver, EncryptablePropertyFilter propertyFilter, MutablePropertySources propSources) {
            ((List)StreamSupport.stream(propSources.spliterator(), false).filter((ps) - {
                return !(ps instanceof EncryptablePropertySource);
            }).map((ps) - {
                return makeEncryptable(interceptionMode, propertyResolver, propertyFilter, ps);
            }).collect(Collectors.toList())).forEach((ps) - {
                propSources.replace(ps.getName(), ps);
            });
        }
    

    关键方法在makeEncryptable中,调用链路很长, 这里选取一条链路跟一下

  • .ulisesbocchio.jasyptspringboot.EncryptablePropertySourceConverter#makeEncryptable
  • com.ulisesbocchio.jasyptspringboot.EncryptablePropertySourceConverter#convertPropertySource
  • com.ulisesbocchio.jasyptspringboot.EncryptablePropertySourceConverter#proxyPropertySource
  • com.ulisesbocchio.jasyptspringboot.aop.EncryptablePropertySourceMethodInterceptor#invoke
  • com.ulisesbocchio.jasyptspringboot.caching.CachingDelegateEncryptablePropertySource#getProperty
  • com.ulisesbocchio.jasyptspringboot.EncryptablePropertySource#getProperty()
  • com.ulisesbocchio.jasyptspringboot.EncryptablePropertySourceConverter#convertPropertySource

    com.ulisesbocchio.jasyptspringboot.aop.EncryptablePropertySourceMethodInterceptor#invoke

    com.ulisesbocchio.jasyptspringboot.EncryptablePropertySource#getProperty()

    看到最后豁然开朗,发现就是用的最开始配置的 DefaultLazyPropertyResolver进行密文解析.

    直接看最终的实现  DefaultPropertyResolver

  • 据lazyEncryptablePropertyDetector过滤需要解密的配置
  • 用lazyEncryptablePropertyDetector去掉前缀后缀
  • 替换占位符
  • 解密
  • 
        public String resolvePropertyValue(String value) {
            Optional var10000 = Optional.ofNullable(value);
            Environment var10001 = this.environment;
            var10001.getClass();
            var10000 = var10000.map(var10001::resolveRequiredPlaceholders);
            EncryptablePropertyDetector var2 = this.detector;
            var2.getClass();
            return (String)var10000.filter(var2::isEncrypted).map((resolvedValue) - {
                try {
                    String unwrappedProperty = this.detector.unwrapEncryptedValue(resolvedValue.trim());
                    String resolvedProperty = this.environment.resolveRequiredPlaceholders(unwrappedProperty);
                    return this.encryptor.decrypt(resolvedProperty);
                } catch (EncryptionOperationNotPossibleException var5) {
                    throw new DecryptionException("Unable to decrypt: " + value + ". Decryption of Properties failed,  make sure encryption/decryption passwords match", var5);
                }
            }).orElse(value);
        }
    

    解惑

    1.加密配置文件能否使用摘要算法,例如md5?

    不能, 配置文件加密是需要解密的,例如数据库连接信息加密,如果不解密,springboot程序无法读取到真正的数据库连接信息,也就无法建立连接.

    2.加密配置文件能否直接使用对称加密,不用PBE?

    可以, PBE的好处就是密码好记.

    3. jasypt.encryptor.password可以泄漏吗?

    不能, 泄漏了等于没有加密.

    4.例子中 jasypt.encryptor.password配置在配置文件中不就等于泄漏了吗?

    是这样的,需要在流程上进行控制.springboot打包时千万不要 jasypt.encryptor.password打入jar包内.

    在公司具体的流程可能是这样的:

  • 运维人员持有`jasypt.encryptor.password`,加密原文获得密文
  • 运维人员将密文发给开发人员
  • 开发人员在配置文件中只配置密文,**不配置**`jasypt.encryptor.password`
  • 运维人员**启动应用时再配置**`jasypt.encryptor.password`
  • 运维人员将密文发给开发人员

    运维人员启动应用时再配置 jasypt.encryptor.password

    如果有其他疑惑欢迎留言提问, 另外由于作者水平有限难免有疏漏, 欢迎留言纠错。

    END

    Java面试题专栏

    实践:使用Jasypt加密SpringBoot配置文件加密springboot配置文件

    我知道你 “在看实践:使用Jasypt加密SpringBoot配置文件加密springboot配置文件

    原文始发于微信公众号(Java知音):

    本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

    本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

    原文链接:blog.ouyangsihai.cn >> 实践——使用Jasypt加密SpringBoot配置文件加密springboot配置文件


     上一篇
    一个普通类就能干趴你的springboot,你信吗? 一个普通类就能干趴你的springboot,你信吗?
    点击上方“Java知音”,选择“置顶公众号” 技术文章第一时间送达! 作者:最后Q泪滴 cnblogs.com/rongdi/p/11780204.html 先声明本人并不是标题党,如果看了本篇文章并且认为没有得到任何收获,
    下一篇 
    Spring Boot Devtools热部署 Spring Boot Devtools热部署
    点击上方“后端技术精选”,选择“置顶公众号” 技术文章第一时间送达! 作者:mrbird mrbird.cc/Spring-Boot-Devtools.html 平日里开发项目中,修改了Java代码或者配置文件的时候,必须手