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

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

相关推荐
wuxuanok34 分钟前
苍穹外卖 —— 公共字段填充
java·开发语言·spring boot·spring·mybatis
汪不止2 小时前
Spring Boot 应用启动机制详解
java·spring boot·后端
matlab的学徒3 小时前
nginx+springboot+redis+mysql+elfk
linux·spring boot·redis·nginx
深色風信子3 小时前
SpringBoot 集成 LangChain4j 本地调用 Ollama
java·spring boot·spring·ollama·langchain4j
EnCi Zheng13 小时前
SpringBoot + PostgreSQL 密码认证失败 Windows 系统解决方案
windows·spring boot·postgresql
EnCi Zheng13 小时前
@ResponseStatus 注解详解
java·spring boot·后端
Arva .14 小时前
开发准备之日志 git
spring boot·git·后端
柳贯一(逆流河版)15 小时前
Spring Boot Actuator+Micrometer:高并发下 JVM 监控体系的轻量化实践
jvm·spring boot·后端
SXJR15 小时前
Spring前置准备(七)——DefaultListableBeanFactory
java·spring boot·后端·spring·源码·spring源码·java开发
java水泥工16 小时前
酒店客房管理系统|基于SpringBoot和Vue的酒店客房管理系统(源码+数据库+文档)
spring boot·vue·酒店管理系统·酒店客房管理系统