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

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

相关推荐
后端小张1 小时前
基于飞算AI的图书管理系统设计与实现
spring boot
咖啡Beans3 小时前
使用OpenFeign实现微服务间通信
java·spring cloud
考虑考虑21 小时前
Jpa使用union all
java·spring boot·后端
咖啡Beans1 天前
SpringCloud网关Gateway功能实现
java·spring cloud
阿杆1 天前
同事嫌参数校验太丑,我直接掏出了更优雅的 SpEL Validator
java·spring boot·后端
昵称为空C2 天前
SpringBoot3 http接口调用新方式RestClient + @HttpExchange像使用Feign一样调用
spring boot·后端
麦兜*2 天前
MongoDB Atlas 云数据库实战:从零搭建全球多节点集群
java·数据库·spring boot·mongodb·spring·spring cloud
麦兜*2 天前
MongoDB 在物联网(IoT)中的应用:海量时序数据处理方案
java·数据库·spring boot·物联网·mongodb·spring
汤姆yu2 天前
基于springboot的毕业旅游一站式定制系统
spring boot·后端·旅游
计算机毕业设计木哥2 天前
计算机毕设选题推荐:基于Java+SpringBoot物品租赁管理系统【源码+文档+调试】
java·vue.js·spring boot·mysql·spark·毕业设计·课程设计