小試牛刀
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包內.
