jlinkarm目标程序和boot文件一起刷_编程实践:使用Jasypt加密SpringBoot配置文件

连接:https://mp.weixin.qq.com/s/jw_AQ6sm7O3SkeDImWB3jg

作者:sunshujie1990

fcc59f02e546b8e42ac11ec2e3760b7d.png

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

        com.github.ulisesbocchio        jasypt-spring-boot-starter        3.0.2

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

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

jasypt.encryptor.password=123abcjasypt.encryptor.algorithm=PBEWithHmacSHA512AndAES_128
@SpringBootTestclass 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=123abcjasypt.encryptor.algorithm=PBEWithHmacSHA512AndAES_128username=ENC(pXDnpH3GdMDBHdxraKyAt7IKCeX8mVlM9A9PeI9Ow2VUoBHRESQ5m8qhrbp45vH+)password=ENC(qD55H3EKYpxp9cGBqpOfR2pqD/AgqT+IyClWKdW80MkHx5jXEViaJTAx6Es4/ZJt)

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

@SpringBootApplicationpublic 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=123abcjasypt.encryptor.algorithm=PBEWithHmacSHA512AndAES_128

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

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

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

PBE加密流程如下

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

PBE解密流程如下

  1. 从密文获取盐
  2. 密码+盐摘要获取秘钥
  3. 密文通过秘钥解密获取原文

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

  • PBE是指用的是PBE加密算法
  • HmacSHA512是指摘要算法,用于获取秘钥
  • AES_128是对称加密算法
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.prefixjasypt.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") Singleton 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这个类

  1. 是一个BeanFactoryPostProcessor
  2. 实现了Ordered,是最低优先级,会在其他BeanFactoryPostProcessor执行之后再执行
  3. postProcessBeanFactory方法中获取了上面提到的两个重要的bean, lazyEncryptablePropertyResolver lazyEncryptablePropertyFilter
  4. 从environment中获取了PropertySources
  5. 调用工具类进行转换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中,调用链路很长, 这里选取一条链路跟一下

  1. .ulisesbocchio.jasyptspringboot.EncryptablePropertySourceConverter#makeEncryptable
  2. com.ulisesbocchio.jasyptspringboot.EncryptablePropertySourceConverter#convertPropertySource
  3. com.ulisesbocchio.jasyptspringboot.EncryptablePropertySourceConverter#proxyPropertySource
  4. com.ulisesbocchio.jasyptspringboot.aop.EncryptablePropertySourceMethodInterceptor#invoke
  5. com.ulisesbocchio.jasyptspringboot.caching.CachingDelegateEncryptablePropertySource#getProperty
  6. com.ulisesbocchio.jasyptspringboot.EncryptablePropertySource#getProperty()

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

直接看最终的实现 DefaultPropertyResolver

  1. 据lazyEncryptablePropertyDetector过滤需要解密的配置
  2. 用lazyEncryptablePropertyDetector去掉前缀后缀
  3. 替换占位符
  4. 解密
    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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值