点击上方“Java知音”,选择“置顶公众号”
技术文章第一时间送达!
小试牛刀
1.构建一个springboot项目,并且引入jasypt依赖
dependency
groupIdcom.github.ulisesbocchio/groupId
artifactIdjasypt-spring-boot-starter/artifactId
version3.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,名字就是加密过程中用的具体算法
HmacSHA512是指摘要算法,用于获取秘钥
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.带着疑问往后看.
@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") SingletonJasyptEncryptorConfigurationProperties 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
这个类
实现了Ordered,是最低优先级,会在其他
BeanFactoryPostProcessor
执行之后再执行
从
environment
中获取了
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中,调用链路很长, 这里选取一条链路跟一下
com.ulisesbocchio.jasyptspringboot.EncryptablePropertySourceConverter#convertPropertySource
com.ulisesbocchio.jasyptspringboot.aop.EncryptablePropertySourceMethodInterceptor#invoke
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
如果有其他疑惑欢迎留言提问, 另外由于作者水平有限难免有疏漏, 欢迎留言纠错。
END
Java面试题专栏
我知道你 “在看”
原文始发于微信公众号(Java知音):