记一次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);
                        }
                    }
                }
            }
        }
    }
}

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

相关推荐
h***59332 小时前
SpringBoot中如何手动开启事务
java·spring boot·spring
q_19132846952 小时前
基于SpringBoot2+Vue2的宠物健康医疗论坛系统
vue.js·spring boot·mysql·健康医疗·宠物·计算机毕业设计
L.EscaRC2 小时前
深入解析SpringBoot中的循环依赖机制与解决方案
java·spring boot·spring·循环依赖
q***48412 小时前
SpringBoot整合easy-es
spring boot·后端·elasticsearch
一 乐3 小时前
健身达人小程序|基于java+vue健身达人小程序的系统设计与实现(源码+数据库+文档)
java·前端·数据库·vue.js·spring boot·小程序
倚肆3 小时前
Spring Boot 中的 Bean 与自动装配详解
spring boot·后端·python
g***96904 小时前
【Spring Boot 实现 PDF 导出】
spring boot·后端·pdf
k***3886 小时前
SpringBoot Test详解
spring boot·后端·log4j
z***89717 小时前
SpringBoot Maven 项目 pom 中的 plugin 插件用法整理
spring boot·后端·maven
j***630810 小时前
Springboot项目中线程池使用整理
java·spring boot·后端