轉自:Java知音 -sunshujie1990
1.構建一個springboot項目,並且引入jasypt依賴
<code><dependency>
<groupid>com.github.ulisesbocchio/<groupid>
<artifactid>jasypt-spring-boot-starter/<artifactid>
<version>3.0.2/<version>
/<dependency>/<code>
2.編寫一個單元測試,用於獲取加密後的賬號密碼
StringEncryptor是jasypt-spring-boot-starter自動配置的加密工具,加密算法我們選擇PBEWithHmacSHA512AndAES_128,password為123abc
<code>jasypt.encryptor.password=123abc
jasypt.encryptor.algorithm=PBEWithHmacSHA512AndAES_128/<code>
<code>@SpringBootTest
classSpringbootPropertiesEncApplicationTests{
@Autowired
privateStringEncryptorstringEncryptor;
@Test
voidcontextLoads(){
Stringsunshujie=stringEncryptor.encrypt("sunshujie");
Stringqwerty1234=stringEncryptor.encrypt("qwerty1234");
System.out.println(sunshujie);
System.out.println(qwerty1234);
}
}/<code>
3.在application.properties中配置加密後的賬號密碼
<code>jasypt.encryptor.password=123abc
jasypt.encryptor.algorithm=PBEWithHmacSHA512AndAES_128
username=ENC(pXDnpH3GdMDBHdxraKyAt7IKCeX8mVlM9A9PeI9Ow2VUoBHRESQ5m8qhrbp45vH+)
password=ENC(qD55H3EKYpxp9cGBqpOfR2pqD/AgqT+IyClWKdW80MkHx5jXEViaJTAx6Es4/ZJt)/<code>
4.觀察在程序中是否能夠拿到解密後的賬號密碼
<code>@SpringBootApplication
publicclassSpringbootPropertiesEncApplicationimplementsCommandLineRunner{
privatestaticfinalLoggerlogger=LoggerFactory.getLogger(SpringbootPropertiesEncApplication.class);
publicstaticvoidmain(String[]args){
SpringApplication.run(SpringbootPropertiesEncApplication.class,args);
}
@Value("${password}")
privateStringpassword;
@Value("${username}")
privateStringusername;
@Override
publicvoidrun(String...args)throwsException{
logger.info("username:{},password:{}",username,password);
}
}/<code>
原理解析
加密原理
首先看jasypt相關的配置,分別是password和加密算法
<code>jasypt.encryptor.password=123abc
jasypt.encryptor.algorithm=PBEWithHmacSHA512AndAES_128/<code>
PBEWithHmacSHA512AndAES_128是此次我們選用的加密算法.
123abc是PBEWithHmacSHA512AndAES_128加密過程中用的加密密碼.
PBE是基於密碼的加密算法,密碼和秘鑰相比有什麼好處呢?好處就是好記…
PBE加密流程如下
密碼加鹽密碼加鹽結果做摘要獲取秘鑰用秘鑰對稱加密原文,然後和鹽拼在一起得到密文PBE解密流程如下
從密文獲取鹽密碼+鹽摘要獲取秘鑰密文通過秘鑰解密獲取原文再來看PBEWithHmacSHA512AndAES_128,名字就是加密過程中用的具體算法
PBE是指用的是PBE加密算法HmacSHA512是指摘要算法,用於獲取秘鑰AES_128是對稱加密算法jasypt-spring-boot-starter原理
先從spring.factories文件入手查看自動配置類
<code>org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.ulisesbocchio.jasyptspringboot.JasyptSpringBootAutoConfiguration/<code>
JasyptSpringBootAutoConfiguration配置僅僅使用@Import註解引入另一個配置類EnableEncryptablePropertiesConfiguration.
<code>@Configuration
@Import({EnableEncryptablePropertiesConfiguration.class})
publicclassJasyptSpringBootAutoConfiguration{
publicJasyptSpringBootAutoConfiguration(){
}
}/<code>
從配置類EnableEncryptablePropertiesConfiguration可以看到有兩個操作
1.@Import了EncryptablePropertyResolverConfiguration.class, CachingConfiguration.class
2.註冊了一個BeanFactoryPostProcessor -> EnableEncryptablePropertiesBeanFactoryPostProcessor
<code>@Configuration
@Import({EncryptablePropertyResolverConfiguration.class,CachingConfiguration.class})
publicclassEnableEncryptablePropertiesConfiguration{
privatestaticfinalLoggerlog=LoggerFactory.getLogger(EnableEncryptablePropertiesConfiguration.class);
publicEnableEncryptablePropertiesConfiguration(){
}
@Bean
publicstaticEnableEncryptablePropertiesBeanFactoryPostProcessorenableEncryptablePropertySourcesPostProcessor(ConfigurableEnvironmentenvironment){
booleanproxyPropertySources=(Boolean)environment.getProperty("jasypt.encryptor.proxy-property-sources",Boolean.TYPE,false);
InterceptionModeinterceptionMode=proxyPropertySources?InterceptionMode.PROXY:InterceptionMode.WRAPPER;
returnnewEnableEncryptablePropertiesBeanFactoryPostProcessor(environment,interceptionMode);
}
}/<code>
先看EncryptablePropertyResolverConfiguration.class
lazyEncryptablePropertyDetector這裡有配置文件中ENC()寫法的出處.從名稱來看是用來找到哪些配置需要解密.
從代碼來看,不一定非得用ENC()把密文包起來, 也可以通過配置來指定其他前綴和後綴
<code>jasypt.encryptor.property.prefix
jasypt.encryptor.property.suffix/<code>
<code>@Bean(
name={"lazyEncryptablePropertyDetector"}
)
publicEncryptablePropertyDetectorencryptablePropertyDetector(EncryptablePropertyResolverConfiguration.EnvCopyenvCopy,BeanFactorybf){
Stringprefix=envCopy.get().resolveRequiredPlaceholders("${jasypt.encryptor.property.prefix:ENC(}");
Stringsuffix=envCopy.get().resolveRequiredPlaceholders("${jasypt.encryptor.property.suffix:)}");
StringcustomDetectorBeanName=envCopy.get().resolveRequiredPlaceholders(DETECTOR_BEAN_PLACEHOLDER);
booleanisCustom=envCopy.get().containsProperty("jasypt.encryptor.property.detector-bean");
returnnewDefaultLazyPropertyDetector(prefix,suffix,customDetectorBeanName,isCustom,bf);
}/<code>
另外還配置了很多bean,先記住這兩個重要的bean.帶著疑問往後看.
lazyEncryptablePropertyResolver 加密屬性解析器lazyEncryptablePropertyFilter 加密屬性過濾器<code>@Bean(
name={"lazyEncryptablePropertyResolver"}
)
publicEncryptablePropertyResolverencryptablePropertyResolver(@Qualifier("lazyEncryptablePropertyDetector")EncryptablePropertyDetectorpropertyDetector,@Qualifier("lazyJasyptStringEncryptor")StringEncryptorencryptor,BeanFactorybf,EncryptablePropertyResolverConfiguration.EnvCopyenvCopy,ConfigurableEnvironmentenvironment){
StringcustomResolverBeanName=envCopy.get().resolveRequiredPlaceholders(RESOLVER_BEAN_PLACEHOLDER);
booleanisCustom=envCopy.get().containsProperty("jasypt.encryptor.property.resolver-bean");
returnnewDefaultLazyPropertyResolver(propertyDetector,encryptor,customResolverBeanName,isCustom,bf,environment);
}
@Bean(
name={"lazyEncryptablePropertyFilter"}
)
publicEncryptablePropertyFilterencryptablePropertyFilter(EncryptablePropertyResolverConfiguration.EnvCopyenvCopy,ConfigurableBeanFactorybf,@Qualifier("configPropsSingleton")Singleton<jasyptencryptorconfigurationproperties>configProps){
StringcustomFilterBeanName=envCopy.get().resolveRequiredPlaceholders(FILTER_BEAN_PLACEHOLDER);
booleanisCustom=envCopy.get().containsProperty("jasypt.encryptor.property.filter-bean");
FilterConfigurationPropertiesfilterConfig=((JasyptEncryptorConfigurationProperties)configProps.get()).getProperty().getFilter();
returnnewDefaultLazyPropertyFilter(filterConfig.getIncludeSources(),filterConfig.getExcludeSources(),filterConfig.getIncludeNames(),filterConfig.getExcludeNames(),customFilterBeanName,isCustom,bf);
}/<jasyptencryptorconfigurationproperties>/<code>
再看EnableEncryptablePropertiesBeanFactoryPostProcessor這個類
是一個BeanFactoryPostProcessor實現了Ordered,是最低優先級,會在其他BeanFactoryPostProcessor執行之後再執行postProcessBeanFactory方法中獲取了上面提到的兩個重要的bean, lazyEncryptablePropertyResolver lazyEncryptablePropertyFilter從environment中獲取了PropertySources調用工具類進行轉換PropertySources, 也就是把密文轉換為原文<code>publicclassEnableEncryptablePropertiesBeanFactoryPostProcessorimplementsBeanFactoryPostProcessor,Ordered{
//ignoresomecode
publicvoidpostProcessBeanFactory(ConfigurableListableBeanFactorybeanFactory)throwsBeansException{
LOG.info("Post-processingPropertySourceinstances");
EncryptablePropertyResolverpropertyResolver=(EncryptablePropertyResolver)beanFactory.getBean("lazyEncryptablePropertyResolver",EncryptablePropertyResolver.class);
EncryptablePropertyFilterpropertyFilter=(EncryptablePropertyFilter)beanFactory.getBean("lazyEncryptablePropertyFilter",EncryptablePropertyFilter.class);
MutablePropertySourcespropSources=this.environment.getPropertySources();
EncryptablePropertySourceConverter.convertPropertySources(this.interceptionMode,propertyResolver,propertyFilter,propSources);
}
publicintgetOrder(){
return2147483547;
}
}/<code>
再看工具類EncryptablePropertySourceConverter
1.過濾所有已經是EncryptablePropertySource的PropertySource
2.轉換為EncryptablePropertySource
3.用EncryptablePropertySource從PropertySources中替換原PropertySource
<code>publicstaticvoidconvertPropertySources(InterceptionModeinterceptionMode,EncryptablePropertyResolverpropertyResolver,EncryptablePropertyFilterpropertyFilter,MutablePropertySourcespropSources){
((List)StreamSupport.stream(propSources.spliterator(),false).filter((ps)->{
return!(psinstanceofEncryptablePropertySource);
}).map((ps)->{
returnmakeEncryptable(interceptionMode,propertyResolver,propertyFilter,ps);
}).collect(Collectors.toList())).forEach((ps)->{
propSources.replace(ps.getName(),ps);
});
}/<code>
關鍵方法在makeEncryptable中,調用鏈路很長, 這裡選取一條鏈路跟一下
.ulisesbocchio.jasyptspringboot.EncryptablePropertySourceConverter#makeEncryptablecom.ulisesbocchio.jasyptspringboot.EncryptablePropertySourceConverter#convertPropertySourcecom.ulisesbocchio.jasyptspringboot.EncryptablePropertySourceConverter#proxyPropertySourcecom.ulisesbocchio.jasyptspringboot.aop.EncryptablePropertySourceMethodInterceptor#invokecom.ulisesbocchio.jasyptspringboot.caching.CachingDelegateEncryptablePropertySource#getPropertycom.ulisesbocchio.jasyptspringboot.EncryptablePropertySource#getProperty()看到最後豁然開朗,發現就是用的最開始配置的DefaultLazyPropertyResolver進行密文解析.
直接看最終的實現 DefaultPropertyResolver
據lazyEncryptablePropertyDetector過濾需要解密的配置用lazyEncryptablePropertyDetector去掉前綴後綴替換佔位符解密<code>publicStringresolvePropertyValue(Stringvalue){
Optionalvar10000=Optional.ofNullable(value);
Environmentvar10001=this.environment;
var10001.getClass();
var10000=var10000.map(var10001::resolveRequiredPlaceholders);
EncryptablePropertyDetectorvar2=this.detector;
var2.getClass();
return(String)var10000.filter(var2::isEncrypted).map((resolvedValue)->{
try{
StringunwrappedProperty=this.detector.unwrapEncryptedValue(resolvedValue.trim());
StringresolvedProperty=this.environment.resolveRequiredPlaceholders(unwrappedProperty);
returnthis.encryptor.decrypt(resolvedProperty);
}catch(EncryptionOperationNotPossibleExceptionvar5){
thrownewDecryptionException("Unabletodecrypt:"+value+".DecryptionofPropertiesfailed,makesureencryption/decryptionpasswordsmatch",var5);
}
}).orElse(value);
}/<code>
解惑
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