记一次nacos配置文件解密插件不生效的问题

原来配置文件走nacos都是用的nacos config的方式配置的,比如如下的配置方式:

yaml 复制代码
spring:
  application:
    name: test
  cloud:
    nacos:
      config:
        enabled: true
        server-addr: ${nacos.server-addr}
        refresh-enabled: true
        namespace: ${nacos.namespace}
        extension-configs:
          - data-id: ${spring.application.name}.yml
            group: ${spring.cloud.nacos.config.group}
            refresh: true
          - data-id: spring-datasource.yml
            group: ${spring.cloud.nacos.config.group}
            refresh: true
          - data-id: spring-redis.yml
            group: ${spring.cloud.nacos.config.group}
            refresh: true
        file-extension: yaml
        group: DEFAULT_GROUP

这种方式,如果nacos配置的密码是加密的,只需写一个类(CustomNacosPropertySourceLocator)继承NacosPropertySourceLocator,启动的时候覆盖掉原来的NacosPropertySourceLocator,就能解密密码。 但是最近新项目采用了springboot3来实现的,加载文件换了一种方式,走的如下的配置方式:

yaml 复制代码
spring:
  application:
    name: test
  config:
    import:
    - classpath:spring-nacos.yml
    - nacos:spring-datasource.yml

spring.config.import是springboot2.4以后加的加载config的方式,支持加载外部文件,以及配置中心定义的文件。 这种时候自定义的CustomNacosPropertySourceLocator就不起作用了。PropertySourceLocator是springcloud中Bootstrap中接口,Spring Cloud 2020.0.0版本之后,为了简化依赖管理,spring-cloud-starter-bootstrap不再默认包含在其他starter中,如果需要开启bootstrap需要引入

xml 复制代码
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>

这时bootstrap.yml文件是生效的。下面分析一下PropertySourceLocator接口是怎么生效的。
PropertySourceBootstrapConfiguration负责在bootstrap阶段加载配置文件。其中有一段源码是这样的

ini 复制代码
private void doInitialize(ConfigurableApplicationContext applicationContext) {
    List<PropertySource<?>> composite = new ArrayList<>();
    AnnotationAwareOrderComparator.sort(this.propertySourceLocators);
    boolean empty = true;
    ConfigurableEnvironment environment = applicationContext.getEnvironment();
    for (PropertySourceLocator locator : this.propertySourceLocators) {
       Collection<PropertySource<?>> source = locator.locateCollection(environment);
       if (source == null || source.size() == 0) {
          continue;
       }
       List<PropertySource<?>> sourceList = new ArrayList<>();
       for (PropertySource<?> p : source) {
          if (p instanceof EnumerablePropertySource<?> enumerable) {
             sourceList.add(new BootstrapPropertySource<>(enumerable));
          }
          else {
             sourceList.add(new SimpleBootstrapPropertySource(p));
          }
       }
       logger.info("Located property source: " + sourceList);
       composite.addAll(sourceList);
       empty = false;
    }
    if (!empty) {
       MutablePropertySources propertySources = environment.getPropertySources();
       String logConfig = environment.resolvePlaceholders("${logging.config:}");
       LogFile logFile = LogFile.get(environment);
       for (PropertySource<?> p : environment.getPropertySources()) {
          if (p.getName().startsWith(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
             propertySources.remove(p.getName());
          }
       }
       insertPropertySources(propertySources, composite);
       reinitializeLoggingSystem(environment);
       setLogLevels(applicationContext, environment);
       handleProfiles(environment);
    }
}

其中this.propertySourceLocators是spring容器中注册的所有的PropertySourceLocator实例。在bootstrap阶段会遍历所有的PropertySourceLocator来调用它的locateColletion方法。再看一下NacosPropertySourceLocator是怎么实现的。它的locateCollection用的默认实现

ini 复制代码
static Collection<PropertySource<?>> locateCollection(PropertySourceLocator locator, Environment environment) {
    PropertySource<?> propertySource = locator.locate(environment);
    if (propertySource == null) {
       return Collections.emptyList();
    }
    if (propertySource instanceof CompositePropertySource) {
       Collection<PropertySource<?>> sources = ((CompositePropertySource) propertySource).getPropertySources();
       List<PropertySource<?>> filteredSources = new ArrayList<>();
       for (PropertySource<?> p : sources) {
          if (p != null) {
             filteredSources.add(p);
          }
       }
       return filteredSources;
    }
    else {
       return List.of(propertySource);
    }
}

默认实现会调用locate方法,所以再看一下locate方法是怎么实现的

ini 复制代码
@Override
public PropertySource<?> locate(Environment env) {
    nacosConfigProperties.setEnvironment(env);
    ConfigService configService = nacosConfigManager.getConfigService();

    if (null == configService) {
       log.warn("no instance of config service found, can't load config from nacos");
       return null;
    }
    long timeout = nacosConfigProperties.getTimeout();
    nacosPropertySourceBuilder = new NacosPropertySourceBuilder(configService,
          timeout);
    String name = nacosConfigProperties.getName();

    String dataIdPrefix = nacosConfigProperties.getPrefix();
    if (StringUtils.isEmpty(dataIdPrefix)) {
       dataIdPrefix = name;
    }

    if (StringUtils.isEmpty(dataIdPrefix)) {
       dataIdPrefix = env.getProperty("spring.application.name");
    }

    CompositePropertySource composite = new CompositePropertySource(
          NACOS_PROPERTY_SOURCE_NAME);

    loadSharedConfiguration(composite);
    loadExtConfiguration(composite);
    loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env);
    return composite;
}

最后调用了loadSharedConfiguration、loadExtConfiguration、loadApplicationConfiguration三个方法,其中loadExtConfiguration加载的就是在配置文件中配置的extension-configs中的配置文件,在spring.config.import中配置的文件是无法识别的,所以会失效。

改用其他的实现方案,实现EnvironmentPostProcessor接口,主要方法:

ini 复制代码
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
    MutablePropertySources sources = environment.getPropertySources();
    String nacosSecret = getKey(environment);
    for (PropertySource<?> source : sources) {
        if (source instanceof EnumerablePropertySource) {
            Map<String, Object> map = (Map)source.getSource();
            for(Map.Entry<String, Object> entry : map.entrySet()) {
                String key = entry.getKey();
                if (entry.getValue() != null) {
                    String value = entry.getValue().toString();
                    if (value.contains("ENC(")) {
                        Matcher matcher = ENC_PATTERN.matcher(value);
                        if (matcher.find()) {
                            String encryptedStr = matcher.group(1);
                            String decryptVal = null;
                            try {
                                decryptVal = AesUtils.decrypt(encryptedStr, nacosSecret);
                            } catch (Exception e) {
                                throw new RuntimeException(e);
                            }
                            String val = value.replace("ENC(" + encryptedStr + ")", decryptVal);
                            System.out.println(val);
                            map.put(key, val);
                        }
                    }
                }
            }
        }
    }
}

这样就可以实现配置文件解密了

相关推荐
zhz52141 小时前
从PostgreSQL到人大金仓(KingBase)数据库迁移实战:Spring Boot项目完整迁移指南
数据库·spring boot·postgresql
亦安✘1 小时前
服务器从0到1微服务所需的环境的安装
运维·服务器·spring cloud·微服务
叫我阿柒啊3 小时前
Java全栈开发面试实战:从基础到复杂场景的深度解析
java·数据库·spring boot·面试·vue·测试·全栈开发
3Cloudream4 小时前
互联网大厂Java面试实录:Spring Boot与微服务架构解析
spring boot·微服务·hibernate·jwt·java面试
风象南4 小时前
SpringBoot 程序 CPU 飙升排查:自制「方法级采样火焰图」
spring boot·后端
用户6120414922134 小时前
springboot+vue3做的图书管理与借阅系统
vue.js·spring boot·后端
友莘居士4 小时前
springbootr如何调用dolphinshceduler
spring boot·restful·dolphin·shceduler
仙俊红10 小时前
Spring Boot `@Configuration` 与 `@Component` 笔记
java·spring boot·笔记
微扬嘴角13 小时前
springcloud篇5-微服务保护(Sentinel)
spring cloud·微服务·sentinel