技术文章第一时间送达!php
1.构建一个springboot项目,而且引入jasypt依赖git
<dependency> <groupId>com.github.ulisesbocchio</groupId> <artifactId>jasypt-spring-boot-starter</artifactId> <version>3.0.2</version> </dependency>
2.编写一个单元测试,用于获取加密后的帐号密码github
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中配置加密后的帐号密码redis
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和加密算法spring
jasypt.encryptor.password=123abc jasypt.encryptor.algorithm=PBEWithHmacSHA512AndAES_128
PBEWithHmacSHA512AndAES_128是这次咱们选用的加密算法.数据库
123abc是PBEWithHmacSHA512AndAES_128加密过程当中用的加密密码.springboot
PBE是基于密码的加密算法,密码和秘钥相比有什么好处呢?好处就是好记…app
PBE加密流程以下
密码加盐
密码加盐结果作摘要获取秘钥
用秘钥对称加密原文,而后和盐拼在一块儿获得密文
PBE解密流程以下
从密文获取盐
密码+盐摘要获取秘钥
密文经过秘钥解密获取原文
再来看PBEWithHmacSHA512AndAES_128,名字就是加密过程当中用的具体算法
PBE是指用的是PBE加密算法
HmacSHA512是指摘要算法,用于获取秘钥
AES_128是对称加密算法
先从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
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
若是有其余疑惑欢迎留言提问, 另外因为做者水平有限不免有疏漏, 欢迎留言纠错。
END
Java面试题专栏
【40期】说一下线程池内部工做原理 【39期】Mybatis面试18问,你想知道的都在这里了! 【38期】一份tcp、http面试指南,常考点都给你了 【37期】请你详细说说类加载流程,类加载机制及自定义类加载器 【36期】说说 如何中止一个正在运行的线程? 【35期】谈谈你对Java线程之间通讯方式的理解 【34期】谈谈为何要拆分数据库?有哪些方法? 【33期】分别谈谈联合索引生效和失效的条件 【32期】你知道Redis的字符串是怎么实现的吗? 【31期】了解什么是 redis 的雪崩、穿透和击穿?redis 崩溃以后会怎么样?应对措施是什么