2

spring cloud与加密库jasypt(ulisesbocchio)冲突问题定位 - 三国梦回

 7 months ago
source link: https://www.cnblogs.com/grey-wolf/p/18006844
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

背景#

最近在项目上遇到个问题。项目就是普通的spring cloud,spring cloud在spring boot的基础上多了一些东西,比如支持bootstrap上下文(通过bootstrap.yml/properties配置)。另外呢,我们这边要求上线的时候,要把配置文件里的敏感配置如密码,进行加密。加密的话,我们这边用了如下库:

<dependency>
    <groupId>com.github.ulisesbocchio</groupId>
    <artifactId>jasypt-spring-boot-starter</artifactId>
    <version>3.0.5</version>
</dependency>

https://github.com/ulisesbocchio/jasypt-spring-boot

加密后,配置文件里敏感属性就长这样:

secret.property=ENC(nrmZtkF7T0kjG/VodDvBw93Ct8EgjCA+)

程序启动时,会加载配置文件,发现值是ENC()格式,就会自动解密为明文。

这次,在如下这种场景中,遇到问题了:

image-20240204164723865

image-20240204164757342

本来没在pom.xml中引入这个包的时候,一切正常;引入后,直接启动都启动不起来了,(注意,我还没开始用这个包的ENC加密那些功能呢),报错大概如下:

Description:

Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.

Reason: Failed to determine a suitable driver class


Action:

Consider the following:
	If you want an embedded database (H2, HSQL or Derby), please put it on the classpath.

是提示找不到url,感觉我的数据库配置没生效一样。

这是怎么一回事呢?

问题定位过程#

检查datasourceProperties#

image-20240204165148871

image-20240204165221695

发现这个配置类有问题,全空。

检查Binder部分#

一般来说,这种配置类都长下面这种:

@ConfigurationProperties(prefix = "spring.datasource")
public class DataSourceProperties{
    private String url;
    ...
}

image-20240204170033873

这个类会被注册到spring应用上下文内,成为一个bean,这部分是通过EnableConfigurationProperties来实现,它会把value对应的class,注册为一个bean:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EnableConfigurationPropertiesRegistrar.class)
public @interface EnableConfigurationProperties {
    Class<?>[] value() default {};
}

上面类中,import的EnableConfigurationPropertiesRegistrar会负责将这些配置类注册到spring:

image-20240204165953833

注册为bean后,DataSourceProperties这个bean中的属性又是从何而来呢,这个就是靠从外部配置文件获取了,如我们这里的application-dev.yaml:

spring:
  application:
    name: property-encrypt-bug
  datasource:
    url: jdbc:mysql://1.1.1.1:3306
    username: root

这部分功能靠如下类实现:

image-20240204170426845

这是一个bean的后置处理器,它是在bean进行初始化之前,来做这件事。

@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    bind(ConfigurationPropertiesBean.get(this.applicationContext, bean, beanName));
    return bean;
}

我们这里打个条件断点:beanName.contains("datasource")

image-20240204170704747

最终来到一个遍历bean中每个属性的地方:

image-20240204170946886

具体到每一个属性呢,则是会遍历一个context.getSources来查找这个属性的值。

image-20240204171402146

值得注意的是,上面的context.getSources中,其实共包含了9个propertySource,这边给大家看下:

image-20240204171921428

但是,这里有个问题是,好像没有我们的application-dev.yml呢?如果这里没有application-dev.yml,那自然是找不到我们的配置了。

我然后做了个对照组,看看没有引入加密包的时候,这里是什么值?

果然不一样。

image-20240204172403987

那就看看这个是怎么来的:

@Override
public Iterable<ConfigurationPropertySource> getSources() {
    if (this.sourcePushCount > 0) {
        return this.source;
    }
    return Binder.this.sources;
}

image-20240204172622358

image-20240204172649769
private Iterable<ConfigurationPropertySource> getConfigurationPropertySources() {
    return ConfigurationPropertySources.from(this.propertySources);
}

image-20240204172724041

image-20240204173850574

image-20240204173918531

这里就可以看到,是从spring中获取了PropertySourcesPlaceholderConfigurer类型的bean,那么,PropertySourcesPlaceholderConfigurer中的数据从哪来呢?

@Nullable
private PropertySources appliedPropertySources;

public PropertySources getAppliedPropertySources() throws IllegalStateException {
    Assert.state(this.appliedPropertySources != null, "PropertySources have not yet been applied");
    return this.appliedPropertySources;
}

而appliedPropertySources字段,则是在方法:PropertySourcesPlaceholderConfigurer#postProcessBeanFactory中赋值的,里面的逻辑是从environment这个bean中获取propertySource。

image-20240204174742643

所以,最终其实还是environment有问题。

检查environment部分#

接上文,看看environment的实际类型:

image-20240204175742591
class ApplicationServletEnvironment extends StandardServletEnvironment {
    private final MutablePropertySources propertySources;
}

这个字段,在正常情况下,包含了applicaion-dev.yml:

image-20240204180028324

异常情况下则没有。

所以,问题就变成了,为什么在异常情况下,environment没有这个yaml。

再进一步跟踪下,发现在默认创建environment时,刚开始的时候,里面只会包含几个初始的:

org.springframework.context.support.AbstractApplicationContext#getEnvironment
@Override
public ConfigurableEnvironment getEnvironment() {
    if (this.environment == null) {
        this.environment = createEnvironment();
    }
    return this.environment;
}

image-20240204180717062

这就有点难搞了,剩下的是啥时候弄进去的呢?

由于这是一个列表,往里面放、取、replace的操作都是可能的,要找到所有这些入口,不那么简单了。

我是在每个增、删、set等方法,全打了断点,一个一个看,但是这种办法也很头疼,稍微错过一个入口,就发现,list的内容变了,还不知道在哪里改动的。

后面我是采用这种方式 + 对照组相结合的方式来debug。

经过长时间的对照和调试,最终才找到了如下位置:

在spring cloud中,我们说会存在bootstrap上下文的创建,bootstrap的处理是在:

org.springframework.cloud.bootstrap.BootstrapApplicationListener#onApplicationEvent

这个类的大体处理逻辑如下:

image-20240204182929455

在environment准备好之后,就会触发上述逻辑,上述逻辑中,会创建bootstrap对应的spring应用上下文容器,见红框标出处。

后面发现,在org.springframework.boot.env.EnvironmentPostProcessorApplicationListener#onApplicationEnvironmentPreparedEvent处,会添加进去bootstrap.yaml这个配置:

image-20240204183830035

再后来,发现在如下处添加了application-dev.yml,完整堆栈如下:

image-20240204184553634
addLast:116, MutablePropertySources (org.springframework.core.env)
applyContributor:352, ConfigDataEnvironment (org.springframework.boot.context.config)
applyToEnvironment:329, ConfigDataEnvironment (org.springframework.boot.context.config)
processAndApply:233, ConfigDataEnvironment (org.springframework.boot.context.config)
postProcessEnvironment:102, ConfigDataEnvironmentPostProcessor (org.springframework.boot.context.config)
postProcessEnvironment:94, ConfigDataEnvironmentPostProcessor (org.springframework.boot.context.config)
onApplicationEnvironmentPreparedEvent:102, EnvironmentPostProcessorApplicationListener (org.springframework.boot.env)
onApplicationEvent:87, EnvironmentPostProcessorApplicationListener (org.springframework.boot.env)
doInvokeListener:178, SimpleApplicationEventMulticaster (org.springframework.context.event)
invokeListener:171, SimpleApplicationEventMulticaster (org.springframework.context.event)
multicastEvent:145, SimpleApplicationEventMulticaster (org.springframework.context.event)
multicastEvent:133, SimpleApplicationEventMulticaster (org.springframework.context.event)
environmentPrepared:85, EventPublishingRunListener (org.springframework.boot.context.event)
lambda$environmentPrepared$2:66, SpringApplicationRunListeners (org.springframework.boot)
accept:-1, 2130192211 (org.springframework.boot.SpringApplicationRunListeners$$Lambda$47)
forEach:1257, ArrayList (java.util)
doWithListeners:120, SpringApplicationRunListeners (org.springframework.boot)
doWithListeners:114, SpringApplicationRunListeners (org.springframework.boot)
environmentPrepared:65, SpringApplicationRunListeners (org.springframework.boot)
prepareEnvironment:344, SpringApplication (org.springframework.boot)
run:302, SpringApplication (org.springframework.boot)
run:1300, SpringApplication (org.springframework.boot)
run:1289, SpringApplication (org.springframework.boot)
main:11, PropertyEncryptBugDemoApplication (com.example.demo)

写这篇文章的此时,我又仔细debug了一阵,发现这块代码还是非常复杂,这一篇先收一收,先把核心答案讲一下,等后续再详细分析源码逻辑。

在正常情况下时(没有加入加密库),如下代码处,是可以正常执行的:

image-20240204185719413

但是,在引入加密库后,加密库会修改propertySource的类型:

image-20240204185854262

所以这里就会不一样,导致这个bootstrap.yml没有识别到,就会引起后续的一系列问题。

总结#

在我找到问题答案后,拿着这块的关键字再去找,果然就发现了一点点资料了。

可以看这个issue,问题一模一样:

https://github.com/ulisesbocchio/jasypt-spring-boot/issues/296

但是没修复,被作者关闭了.

下面也是类似问题:

https://github.com/ulisesbocchio/jasypt-spring-boot/issues/289

解决办法:

最新版本也没修复,可以修改源码,或者先禁用这块功能:jasypt.encryptor.bootstrap=false

image-20240204190352491

博客分类导航贴:博客目录导航,让我们一起学起来吧(持续更新)

我是逐日, 前华为、前鹅厂大头兵,现在成都小公司躺平,可以关注我公号,想起来了就更新;或者右下角有个加号,咱们博客园见

20210108143914.png

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK