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

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

相关推荐
计算机毕设VX:Fegn08951 小时前
计算机毕业设计|基于springboot + vue二手家电管理系统(源码+数据库+文档)
vue.js·spring boot·后端·课程设计
计算机学姐3 小时前
基于SpringBoot的校园资源共享系统【个性化推荐算法+数据可视化统计】
java·vue.js·spring boot·后端·mysql·spring·信息可视化
廋到被风吹走4 小时前
【Spring】Spring Boot 配置管理深度指南:Profile、类型安全与加密
spring boot·安全·spring
BD_Marathon4 小时前
SpringBoot程序快速启动
java·spring boot·后端
万物皆字节5 小时前
Spring Cloud Gateway 启动流程源码分析
java·开发语言·spring boot
a程序小傲5 小时前
得物Java面试被问:方法句柄(MethodHandle)与反射的性能对比和底层区别
java·开发语言·spring boot·后端·python·面试·职场和发展
沙白猿5 小时前
Redis报错:A bean with that name has already been defined in class path resource
spring boot·redis·mybatis
+VX:Fegn08956 小时前
计算机毕业设计|基于springboot + vue物流配送中心信息化管理系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·小程序·课程设计
计算机毕设指导66 小时前
基于微信小程序的钓鱼论坛系统【源码文末联系】
java·spring boot·mysql·微信小程序·小程序·tomcat·maven
qq_12498707536 小时前
基于微信小程序的宠物交易平台的设计与实现(源码+论文+部署+安装)
java·spring boot·后端·微信小程序·小程序·毕业设计·计算机毕业设计