• 解决springboot启动jar包时加载了jar包内的配置文件


    由于生产环境扫描出有开源组件jar包漏洞,于是进行了springboot升级。
    升级后打包部署到生产环境,发现数据库连接报错密码错误。初步定位问题是加载的配置文件是jar包内部而非jar包同级目录的config/application.yml文件。

    版本升级

    组件升级前版本升级后版本
    springcloudHoxton.SR92021.0.3
    springboot2.4.32.6.8

    排查过程

    1. 首先怀疑是生产环境配置出了文件,检查配置文件和服务器环境,没发现有错误。
    2. 接下来在测试环境将配置文件放在config/application.yml,部署此jar包,试图重现此问题,结果并不存在此问题,这就比较奇怪,令人费解。
    3. 怀疑是springboot设计导致配置文件加载顺序或者配置项读取方式有变。查询了相关资料,spring配置文件的加载顺序并没有变化。

    针对jar包内外来说配置文件加载的先后顺序为:

    • jar包外的application.yml
    • jar包内的application.yml
    • jar包外的application-prod.yml

    对于读取顺序来讲为:

    • 优先读取jar包本身同级目录下的config/application.yml;
    • 再读取jar包本身同级目录下的application.yml;
    • classpath根目录下的config目录下的application.yml;
    • classpath根目录下的application.yml。

    如果同时存在.properties和.yml配置文件,会优先加载yml配置。

    1. 于是考虑配置项读取方式,询问项目成员得知他的确修改了一处关于数据库密码解密的代码。由于公司使用的是自己的一套算法对数据库密码进行加密,加密后作为配置项,因此需要对配置的密文进行解密。

    之前获取配置的方式为:

    PropertySource applicationConfig = propertySources.get("applicationConfig");
    
    • 1

    升级后通过上述代码无法获取到配置,因此改为:

    ConfigurableEnvironment environment = ((ApplicationEnvironmentPreparedEvent) event).getEnvironment();
    MutablePropertySources propertySources = environment.getPropertySources();
    Iterator> iterator = propertySources.iterator();
    
    • 1
    • 2
    • 3

    通过阅读代码(看到其中的两层for循环就有种预感),发现的确是代码层面有问题,代码如下:

    ConfigurableEnvironment environment = ((ApplicationEnvironmentPreparedEvent) event).getEnvironment();
    MutablePropertySources propertySources = environment.getPropertySources();
    for (PropertySource propertySource : propertySources) {
        boolean applicationConfig = propertySource.getName().contains("application");
        if (!applicationConfig) {
            continue;
        }
        Object source = propertySource.getSource();
        Map confMap = (Map) source;
        for (Map.Entry entry : confMap.entrySet()) {
            String key = entry.getKey();
            Object value = entry.getValue();
            ...
            // 解密为result
            System.setProperty(key, result);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    从代码看,getPropertySources拿到了所有配置文件,然后在解密配置项时是先从Map中拿到配置,然后解密后将值重新设置到配置项。由于propertySource先执行了jar包外部的配置,然后执行了jar包里面的配置,同一个key的值会被后执行的一次覆盖掉,而MutablePropertySources继承自迭代器Iterable,按说jar包里的配置每次都会覆盖jar包外的。

    解决办法

    找到了原因,修改就简单了。将外层for去除掉,通过ConfigurableEnvironment.getProperty()直接获取配置项的值,springboot会确保每次都是从jar包外部config/application.yml文件读取配置,存在多个需解密的配置项就在类中新建数组存放配置名称。

    String value = environment.getProperty("key");
    
    • 1

    ConfigurableEnvironment类

    从上述可以看到,问题的关键在于如何去干预配置项的读取和设置。于是我们简单看下ConfigurableEnvironment 类的源码。

    public interface ConfigurableEnvironment extends Environment, ConfigurablePropertyResolver {
        //设置活动的配置文件
    	void setActiveProfiles(String... profiles);
        // 增加活动的配置文件
    	void addActiveProfile(String profile);
        // 设置默认的配置文件
    	void setDefaultProfiles(String... profiles);
        // 获取PropertySource键值组合的集合
    	MutablePropertySources getPropertySources();
        // 系统环境变量
    	Map getSystemEnvironment();
        // 系统配置
    	Map getSystemProperties();
        // 合并
    	void merge(ConfigurableEnvironment parent);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
  • 相关阅读:
    Java 反射的应用 - 对象转Map
    SpringBoot项目整合Redis,Rabbitmq发送、消费、存储邮件
    深潜Kotlin协程(二十):构建 Flow
    量化交易之One Piece篇 - linux - 定时任务(重启服务器、执行程序、验证)
    以一道面试题来探讨测试用例设计的六大思路
    Wireshark抓包工具解析HTTPS包
    Chrome 插件开发 V3版本 跨域处理
    开源现场总线协议栈
    深入理解NLP
    ubuntu安装apollo仿真平台
  • 原文地址:https://blog.csdn.net/u800820/article/details/126324864