网站首页 > 博客文章 正文
连接:https://mp.weixin.qq.com/s/jw_AQ6sm7O3SkeDImWB3jg
作者:sunshujie1990
1.构建一个springboot项目,并且引入jasypt依赖
<dependency>
????????<groupId>com.github.ulisesbocchio</groupId>
????????<artifactId>jasypt-spring-boot-starter</artifactId>
????????<version>3.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是对称加密算法
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")?Singleton<JasyptEncryptorConfigurationProperties>?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, 也就是把密文转换为原文
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()
看到最后豁然开朗,发现就是用的最开始配置的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
如果有其他疑惑欢迎留言提问, 另外由于作者水平有限难免有疏漏, 欢迎留言纠错。
猜你喜欢
- 2024-11-13 spring boot配置文件敏感字段加密
- 2024-11-13 Spring Boot数据库密码加密的配置方法
- 2024-11-13 Spring Boot安全之application配置信息加密
- 2024-11-13 如何在SpringBoot项目中实现加密?
- 2024-11-13 SpringBoot进阶-SpringBoot如何实现配置文件脱敏?
- 2024-11-13 加密SpringBoot配置文件技巧(加密pdf文件如何解密编辑)
- 2024-11-13 Spring Boot版本和Jasypt版本的兼容性问题?
- 2024-11-13 SpringBoot加密配置属性(springboot配置密码加密)
- 2024-11-13 SpringCloud-OAuth2(二):实战篇(oauth2.0 springcloud)
- 2024-11-13 SpringBoot 配置文件这样加密,才足够安全!
你 发表评论:
欢迎- 最近发表
- 标签列表
-
- powershellfor (55)
- messagesource (56)
- aspose.pdf破解版 (56)
- promise.race (63)
- 2019cad序列号和密钥激活码 (62)
- window.performance (66)
- qt删除文件夹 (72)
- mysqlcaching_sha2_password (64)
- ubuntu升级gcc (58)
- nacos启动失败 (64)
- ssh-add (70)
- jwt漏洞 (58)
- macos14下载 (58)
- yarnnode (62)
- abstractqueuedsynchronizer (64)
- source~/.bashrc没有那个文件或目录 (65)
- springboot整合activiti工作流 (70)
- jmeter插件下载 (61)
- 抓包分析 (60)
- idea创建mavenweb项目 (65)
- vue回到顶部 (57)
- qcombobox样式表 (68)
- vue数组concat (56)
- tomcatundertow (58)
- pastemac (61)
本文暂时没有评论,来添加一个吧(●'◡'●)