首页 > 其他分享 >spring cloud与加密库jasypt(ulisesbocchio)冲突问题定位

spring cloud与加密库jasypt(ulisesbocchio)冲突问题定位

时间:2024-02-04 19:11:07浏览次数:24  
标签:spring boot ulisesbocchio springframework bean context jasypt org

背景

最近在项目上遇到个问题。项目就是普通的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

标签:spring,boot,ulisesbocchio,springframework,bean,context,jasypt,org
From: https://www.cnblogs.com/grey-wolf/p/18006844

相关文章

  • 开发小技巧 - springboot项目启动控制台输出访问地址
    在SpringBoot项目中,有时我们需要记录或输出访问的地址和IP,以便进行调试、监控或日志记录。以下是如何在SpringBoot中实现这一需求的方法: 1、编写获取所有访问地址工具类packagecom.example.utils;importcn.hutool.core.util.StrUtil;importorg.springframework.b......
  • SpringBoot连接MySQL
    一、文件结构: 二、实体类packagecom.example.demo.domain;importjava.io.Serializable;importjavax.persistence.Column;importjavax.persistence.Entity;importjavax.persistence.GeneratedValue;importjavax.persistence.Id;importjavax.persistence.Table;......
  • SpringBoot 整合 RabbitMQ
    Docker搭建RabbitMQ拉取RabbitMQ的镜像执行命令dockerpullrabbitmq:3.7-management执行运行命令dockerrun-d--hostnamerabbit--namerabbit-eRABBITMQ_DEFAULT_USER=admin-eRABBITMQ_DEFAULT_PASS=admin-p15672:15672rabbitmq:3-management打开浏览器访问......
  • 初中英语优秀范文100篇-079The Spring Festival I will never forget-我永远忘不了的
    PDF格式公众号回复关键字:SHCZFW079记忆树1ThereisnodoubtthattheSpringFestivalisofgreatimportancetoalltheChinese.翻译毫无疑问,春节对所有的中国人来说都非常重要简化记忆春节句子结构There(形式主语)+is(系动词)+nodoubt(表语),使用了现在时,表示“毫无......
  • Springboot项目发布war到tomcat
    springboot项目有些日子没有开发了,新做一个minspringboot项目,复习下项目开发及发布流程。1.新建项目: 2.新建一个业务controllercontroller名称及方法,名称随意,项目结构如下: testcontroller代码文件的内容如下:packagecom.*****.Controller;importorg.springframewor......
  • SpringBoot-热部署插件添加
      在开发中修改代码避免反复重启编译   <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId></dependency> 使用idea为2023.2.3 ......
  • SpringBoot的约定是什么?
    springboot项目中必须在src/main/resources中放入application.yml(yaml,properties)配置文件,名字为applicationspringboot项目中必须在src/main/java中只能有一个启动类......
  • 面试官:SpringCloudGateway过滤器类型有哪些?
    在SpringCloudGateway中,过滤器是在请求到达目标服务之前或之后,执行某些特定操作的一种机制。例如,它可以实现对传入的请求进行验证、修改、日志记录、身份验证、流量控制等各种功能。在SpringCloudGateway中,过滤器总共分为以下两大类:局部过滤器:只作用于某一个路由(route)。全......
  • 【Spring】- 生命周期回调
    【Bean初始化】官方使用建议:建议您不要使用InitializingBean接口,因为它不必要地将代码耦合到Spring。另外,我们建议使用@PostConstruct注解或指定POJO初始化方法。对于基于XML的配置元数据,可以使用init-method属性指定具有无效无参数签名的方法的名称spring:1.org.springf......
  • SpringBoot简单集成JWT
    1.JWT入门1.1JWT概念官方网站:https://jwt.io/introduction/JSONWebToken(JWT)是一个定义在RFC7519开放标准下的技术,提供了一种紧凑且自包含的方式用于在各方之间安全地传输信息。JWT使用JSON对象作为载体,同时通过数字签名来验证和确保信息的可信度。数字签名可以通过......