赞
踩
文章系列
【一、Springboot之Jasypt配置文件加密/解密】
【二、Springboot之Jasypt配置文件加密/解密【源码分析】】
Jasypt 是一个 java 库,可以使开发者不需要太多操作来给 Java 项目添加基本加密功能,而且不需要知道加密原理。Jasypt 为开发人员提供了一种简单易用加密功能,包括:密码认证、字符串加密等。
那么Jasypt在 Springboot 项目中是如何运行的呢???
在 Springboot 中,所以配置文件都是通过 Environment 来管理的,项目启动时,会先初始化 Environment 对象,然后通过 Springboot 自动装配机制,将 Environment 对象封装为 PropertySource,并添加到 MutablePropertySources 中,最后再进行属性注入的时候,通过调用 PropertySource 的 getProperty(String key)
方法,获取并注入属性值。有关 SpringBoot配置文件解析过程详细解析,可查看 《https://blog.csdn.net/qq_33375499/article/details/122651673》。
由此可以猜想,Springboot 在启动过程中,将所以配置文件添加到 MutablePropertySources 对象中,Jasypt 通过将 MutablePropertySources 对象中的所有 PropertySource 进行循环遍历,然后对 PropertySource 进行代理 / 包装,使得在调用 getProperty(String key)
方法时候,能够进行解密处理。最后通过 MutablePropertySources 提供的 replace(String name, PropertySource<?> propertySource)
方法,替换其中的 PropertySource 为 Jasypt的代理类 / 包装类。
Jasypt 整合 Springboot 过程中,借用了 Springboot 自动装配原理,由此可见,入口在 jasypt-spring-boot-starter
jar包中,如下图:
其中 spring.factories
内容为:
// springboot 自动装配机制
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.ulisesbocchio.jasyptspringboot.JasyptSpringBootAutoConfiguration
// Spring Cloud 配置启动加载项
org.springframework.cloud.bootstrap.BootstrapConfiguration=com.ulisesbocchio.jasyptspringboot.JasyptSpringCloudBootstrapConfiguration
JasyptSpringBootAutoConfiguration 类中添加了 @Import
注解(关于 @Import 注解详解,可以参考《 SpringBoot 核心原理》),既将 EnableEncryptablePropertiesConfiguration 导入进 Spring IoC容器中。
@Configuration
@Import({EnableEncryptablePropertiesConfiguration.class})
public class JasyptSpringBootAutoConfiguration {
public JasyptSpringBootAutoConfiguration() {
}
}
@Configuration
@Import({EncryptablePropertyResolverConfiguration.class, CachingConfiguration.class})
@Slf4j
public class EnableEncryptablePropertiesConfiguration {
@Bean
public static EnableEncryptablePropertiesBeanFactoryPostProcessor enableEncryptablePropertySourcesPostProcessor(final ConfigurableEnvironment environment, EncryptablePropertySourceConverter converter) {
return new EnableEncryptablePropertiesBeanFactoryPostProcessor(environment, converter);
}
}
EnableEncryptablePropertiesConfiguration 也添加了 @Import
,但其中导入的 EncryptablePropertyResolverConfiguration、CachingConfiguration 本身并没有特别的。
EncryptablePropertyResolverConfiguration 类主要将 Jasypt 一些核心Bean导入进 Spring IoC 容器中,如:EncryptablePropertySourceConverter(可加密属性源转换器)、EnvCopy(环境copy对象)、JasyptEncryptorConfigurationProperties(Jasypt 加密器配置属性对象)等等,不过需要值得注意的是,在 EncryptablePropertyResolverConfiguration 中,注入了一个 StringEncryptor 类型的 Bean,该 Bean 用于获取自定义解密器类。
@Bean(name = ENCRYPTOR_BEAN_NAME)
public StringEncryptor stringEncryptor(final EnvCopy envCopy, final BeanFactory bf) {
/**
* 获取 自定义加密器 Bean 名称
* ENCRYPTOR_BEAN_PLACEHOLDER = String.format("${%s:jasyptStringEncryptor}", jasypt.encryptor.bean)
*/
final String customEncryptorBeanName = envCopy.get().resolveRequiredPlaceholders(ENCRYPTOR_BEAN_PLACEHOLDER);
// 是否自定义
final boolean isCustom = envCopy.get().containsProperty(ENCRYPTOR_BEAN_PROPERTY);
return new DefaultLazyEncryptor(envCopy.get(), customEncryptorBeanName, isCustom, bf);
}
EnableEncryptablePropertiesConfiguration 在 Jasypt的加解密核心流程中,主要注入了 EnableEncryptablePropertiesBeanFactoryPostProcessor Bean对象。
该类实现了 BeanFactoryPostProcessor 接口,Spring IoC容器在注入该 Bean 进行初始化后,会执行其中的 postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
方法。
public class EnableEncryptablePropertiesBeanFactoryPostProcessor implements BeanFactoryPostProcessor, Ordered { private static final Logger LOG = LoggerFactory.getLogger(EnableEncryptablePropertiesBeanFactoryPostProcessor.class); private final ConfigurableEnvironment environment; private final EncryptablePropertySourceConverter converter; public EnableEncryptablePropertiesBeanFactoryPostProcessor(ConfigurableEnvironment environment, EncryptablePropertySourceConverter converter) { this.environment = environment; this.converter = converter; } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { LOG.info("Post-processing PropertySource instances"); // 获取 environment 中所有的 MutablePropertySources MutablePropertySources propSources = environment.getPropertySources(); // 通过 converter 将其中所有的PropertySource进行转换包装:用于在调用 propertySource.getProperty(String name) 时,进行解密 converter.convertPropertySources(propSources); } @Override public int getOrder() { return Ordered.LOWEST_PRECEDENCE - 100; } }
EnableEncryptablePropertiesBeanFactoryPostProcessor 主要左右是,启用可加密属性,将environment 中所有的 MutablePropertySources的PropertySource进行转换包装,使得Spring框架在调用 propertySource.getProperty(String name) 时,执行解密方法,进而注入明文到对应的实体类中。
public class EncryptablePropertySourceConverter { /** * 转换 */ public void convertPropertySources(MutablePropertySources propSources) { StreamSupport.stream(propSources.spliterator(), false) // 过滤获取非EncryptablePropertySource的PropertySources .filter(ps -> !(ps instanceof EncryptablePropertySource)) // 将所有的非EncryptablePropertySource的PropertySource转换成EncryptablePropertySource .map(this::makeEncryptable) .collect(toList()) // 调用 MutablePropertySources.replace()方法 // 将转换之后的EncryptablePropertySource替换到MutablePropertySources中 .forEach(ps -> propSources.replace(ps.getName(), ps)); } }
EncryptablePropertySourceConverter.convertPropertySources(MutablePropertySources propSources)
方法主要核心逻辑是将 Spring 环境变量中的 PropertySource 对象,转换为 Jasypt 中的 EncryptablePropertySource 对象,使得 Spring 在调用 propertySource.getProperty() 方法时,能够通过代理模式或包装器模式执行 EncryptablePropertySource 中的解密方法,从而完成配置文件的解密过程。
public class EncryptablePropertySourceConverter { /** * 转换PropertySource对象 */ public <T> PropertySource<T> makeEncryptable(PropertySource<T> propertySource) { // 如果已经失 EncryptablePropertySource 对象,或 存在skipPropertySourceClasses(跳过属性源类)中,直接返回 if (propertySource instanceof EncryptablePropertySource || skipPropertySourceClasses.stream().anyMatch(skipClass -> skipClass.equals(propertySource.getClass()))) { log.info("Skipping PropertySource {} [{}", propertySource.getName(), propertySource.getClass()); return propertySource; } // 转换PropertySource对象 PropertySource<T> encryptablePropertySource = convertPropertySource(propertySource); log.info("Converting PropertySource {} [{}] to {}", propertySource.getName(), propertySource.getClass().getName(), AopUtils.isAopProxy(encryptablePropertySource) ? "AOP Proxy" : encryptablePropertySource.getClass().getSimpleName()); return encryptablePropertySource; } }
public class EncryptablePropertySourceConverter { private <T> PropertySource<T> convertPropertySource(PropertySource<T> propertySource) { // 判断采用什么模式:InterceptionMode.PROXY--->代理、InterceptionMode.WRAPPER--->包装 return interceptionMode == InterceptionMode.PROXY ? proxyPropertySource(propertySource) : instantiatePropertySource(propertySource); } /** * 代理 */ private <T> PropertySource<T> proxyPropertySource(PropertySource<T> propertySource) { //Silly Chris Beams for making CommandLinePropertySource getProperty and containsProperty methods final. Those methods //can't be proxied with CGLib because of it. So fallback to wrapper for Command Line Arguments only. // 判断是否能被代理,如果不能,走包装器代码逻辑 if (CommandLinePropertySource.class.isAssignableFrom(propertySource.getClass()) // Other PropertySource classes like org.springframework.boot.env.OriginTrackedMapPropertySource // are final classes as well || Modifier.isFinal(propertySource.getClass().getModifiers())) { return instantiatePropertySource(propertySource); } // 创建代理对象 ProxyFactory proxyFactory = new ProxyFactory(); proxyFactory.setTargetClass(propertySource.getClass()); proxyFactory.setProxyTargetClass(true); proxyFactory.addInterface(EncryptablePropertySource.class); proxyFactory.setTarget(propertySource); // EncryptablePropertySourceMethodInterceptor类实现了Interceptor接口,代理模式,直接走其中的 invoke 方法逻辑 proxyFactory.addAdvice(new EncryptablePropertySourceMethodInterceptor<>(propertySource, propertyResolver, propertyFilter)); return (PropertySource<T>) proxyFactory.getProxy(); } /** * 初始化 PropertySource 对象,采用代理模式或者包装器模式 */ private <T> PropertySource<T> instantiatePropertySource(PropertySource<T> propertySource) { PropertySource<T> encryptablePropertySource; // 无论如何都需要代理 if (needsProxyAnyway(propertySource)) { encryptablePropertySource = proxyPropertySource(propertySource); } else if (propertySource instanceof SystemEnvironmentPropertySource) { // 包装器 encryptablePropertySource = (PropertySource<T>) new EncryptableSystemEnvironmentPropertySourceWrapper((SystemEnvironmentPropertySource) propertySource, propertyResolver, propertyFilter); } else if (propertySource instanceof MapPropertySource) { // 包装器 encryptablePropertySource = (PropertySource<T>) new EncryptableMapPropertySourceWrapper((MapPropertySource) propertySource, propertyResolver, propertyFilter); } else if (propertySource instanceof EnumerablePropertySource) { // 包装器 encryptablePropertySource = new EncryptableEnumerablePropertySourceWrapper<>((EnumerablePropertySource) propertySource, propertyResolver, propertyFilter); } else { // 包装器 encryptablePropertySource = new EncryptablePropertySourceWrapper<>(propertySource, propertyResolver, propertyFilter); } return encryptablePropertySource; } }
最后,通过代码分析,你会发行,不管采用代理模式,还是包装器模式,最终调用的都是 CachingDelegateEncryptablePropertySource 中的 getProperty(String name)
方法。
首先,先看看 CachingDelegateEncryptablePropertySource 的类结构图,如下,继承了 org.springframework.core.env.PropertySource
类,重写了其中的 Object getProperty(String name)
方法。
public class CachingDelegateEncryptablePropertySource<T> extends PropertySource<T> implements EncryptablePropertySource<T> { @Override public Object getProperty(String name) { // Can be called recursively, so, we cannot use computeIfAbsent. // 是否存在缓存 if (cache.containsKey(name)) { return cache.get(name); } synchronized (this) { // 双重校验 if (!cache.containsKey(name)) { /** * 调用 EncryptablePropertySource 的 getProperty 方法 * resolver:密码解释器 * filter:过滤器 * delegate:PropertySource 对象,用于获取密文 * name:配置文件key值 */ Object resolved = getProperty(resolver, filter, delegate, name); // 不为空,缓存 if (resolved != null) { cache.put(name, resolved); } } return cache.get(name); } } }
通过代码分析,在 Springboot 与 Jasypt 整合中,最终调用的是 EncryptablePropertySource.getProperty
方法,该方法通过其中的 EncryptablePropertyResolver resolver
参数,将密文进行解析。
public interface EncryptablePropertySource<T> { PropertySource<T> getDelegate(); Object getProperty(String name); void refresh(); /** * getProperty 方法 * resolver:密码解释器 * filter:过滤器 * delegate:PropertySource 对象,用于获取密文 * name:配置文件key值 */ default Object getProperty(EncryptablePropertyResolver resolver, EncryptablePropertyFilter filter, PropertySource<T> source, String name) { // 获取配置文件中的值 Object value = source.getProperty(name); if (filter.shouldInclude(source, name) && value instanceof String) { String stringValue = String.valueOf(value); // 解密 return resolver.resolvePropertyValue(stringValue); } return value; } }
希望来了,终极类!!!!!
经过上面的 Springboot Jasypt源码分析 可知,最终调用的是 EncryptablePropertyResolver.resolvePropertyValue(String value)
方法,该方法将需要解密的字符串进行解密返回,代码如下:
public class DefaultPropertyResolver implements EncryptablePropertyResolver { @Override public String resolvePropertyValue(String value) { return Optional.ofNullable(value) // 处理占位符 .map(environment::resolvePlaceholders) // 判断是否已加密:是否以默认prefix = "ENC(", suffix = ")"前缀开头,后缀结尾 // 这也是配置文件中为什么需要 ENC() 包裹密文的原因 .filter(detector::isEncrypted) .map(resolvedValue -> { try { // 去除前缀、后缀,默认prefix = "ENC(", suffix = ")" String unwrappedProperty = detector.unwrapEncryptedValue(resolvedValue.trim()); // 处理占位符 String resolvedProperty = environment.resolvePlaceholders(unwrappedProperty); // 解密 return encryptor.decrypt(resolvedProperty); } catch (EncryptionOperationNotPossibleException e) { throw new DecryptionException("Unable to decrypt: " + value + ". Decryption of Properties failed, make sure encryption/decryption " + "passwords match", e); } }) // 如果没有加密,直接返回value值 .orElse(value); } }
在 Jasypt 中,StringEncryptor 接口存在四种默认实现,分别是:
其中的 MyStringEncryptor 为作者自定义加解密器。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。