讓Nacos支持加密配置項的配置刷新


2021年4月1日(本內容非愚人節惡搞)新增:

jasypt在新版本3.0.3.Release中已經解決了這個問題,請大家直接用新版就行。

 

 

傳送門

————————————————————————————————————————————————以下為原文——————————————————————————————————————————————————————————————————

 

使用版本:

SpringBoot:2.0.6

jasypt-spring-boot-starter:2.0.0

spring-cloud-starter-alibaba-nacos-config:2.0.1.RELEASE

 

問題現象:

首次通過Nacos配置中心獲取配置,對於ENC(XXXX)能正常解密,后續修改配置中心配置,觸發配置刷新后,無法正常解密,配置內容變為ENC(XXXX)

 

源碼解析分析參考:

https://blog.csdn.net/u013905744/article/details/86508236

 

在對jasypt和nacos的源碼進行分析后,做了一些不太優雅的改動。具體方式如下:

1、在src目錄下新建包,路徑為:com.alibaba.cloud.nacos.client

2、將NacosPropertySourceLocator.java的源碼進行修改,並貼到上面的包路徑下,修改后代碼為:

/*
 * Copyright (C) 2018 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.alibaba.cloud.nacos.client;

import com.alibaba.cloud.nacos.NacosConfigProperties;
import com.alibaba.cloud.nacos.NacosPropertySourceRepository;
import com.alibaba.cloud.nacos.parser.NacosDataParserHandler;
import com.alibaba.cloud.nacos.refresh.NacosContextRefresher;
import com.alibaba.nacos.api.config.ConfigService;
import com.ulisesbocchio.jasyptspringboot.EncryptablePropertyResolver;
import com.ulisesbocchio.jasyptspringboot.EncryptablePropertySourceConverter;
import com.ulisesbocchio.jasyptspringboot.InterceptionMode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.cloud.bootstrap.config.PropertySourceLocator;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertySource;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.List;

import static com.ulisesbocchio.jasyptspringboot.configuration.EncryptablePropertyResolverConfiguration.RESOLVER_BEAN_NAME;

/**
 * @author xiaojing
 * @author pbting
 * @author liuyuxiang
 */
@Order(0)
public class NacosPropertySourceLocator implements PropertySourceLocator {

    private static final Logger log = LoggerFactory
            .getLogger(NacosPropertySourceLocator.class);

    private static final String NACOS_PROPERTY_SOURCE_NAME = "NACOS";

    private static final String SEP1 = "-";

    private static final String DOT = ".";

    private static final String SHARED_CONFIG_SEPARATOR_CHAR = "[,]";

    private NacosPropertySourceBuilder nacosPropertySourceBuilder;

    private NacosConfigProperties nacosConfigProperties;

    @Autowired
    private ConfigurableListableBeanFactory beanFactory;
    @Autowired
    private ConfigurableApplicationContext context;

    public NacosPropertySourceLocator(NacosConfigProperties nacosConfigProperties) {
        this.nacosConfigProperties = nacosConfigProperties;
    }

    @Override
    public PropertySource<?> locate(Environment env) {

        ConfigService configService = nacosConfigProperties.configServiceInstance();

        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);
 EncryptablePropertyResolver propertyResolver = beanFactory.getBean(RESOLVER_BEAN_NAME, EncryptablePropertyResolver.class); return EncryptablePropertySourceConverter.makeEncryptable(InterceptionMode.PROXY, propertyResolver, composite);
    }

    private void loadSharedConfiguration(
            CompositePropertySource compositePropertySource) {
        String sharedDataIds = nacosConfigProperties.getSharedDataids();
        String refreshDataIds = nacosConfigProperties.getRefreshableDataids();

        if (sharedDataIds == null || sharedDataIds.trim().length() == 0) {
            return;
        }

        String[] sharedDataIdArray = sharedDataIds.split(SHARED_CONFIG_SEPARATOR_CHAR);
        checkDataIdFileExtension(sharedDataIdArray);

        for (int i = 0; i < sharedDataIdArray.length; i++) {
            String dataId = sharedDataIdArray[i];
            String fileExtension = dataId.substring(dataId.lastIndexOf(".") + 1);
            boolean isRefreshable = checkDataIdIsRefreshable(refreshDataIds,
                    sharedDataIdArray[i]);

            loadNacosDataIfPresent(compositePropertySource, dataId, "DEFAULT_GROUP",
                    fileExtension, isRefreshable);
        }
    }

    private void loadExtConfiguration(CompositePropertySource compositePropertySource) {
        List<NacosConfigProperties.Config> extConfigs = nacosConfigProperties
                .getExtConfig();

        if (CollectionUtils.isEmpty(extConfigs)) {
            return;
        }

        checkExtConfiguration(extConfigs);

        for (NacosConfigProperties.Config config : extConfigs) {
            String dataId = config.getDataId();
            String fileExtension = dataId.substring(dataId.lastIndexOf(DOT) + 1);
            loadNacosDataIfPresent(compositePropertySource, dataId, config.getGroup(),
                    fileExtension, config.isRefresh());
        }
    }

    private void checkExtConfiguration(List<NacosConfigProperties.Config> extConfigs) {
        String[] dataIds = new String[extConfigs.size()];
        for (int i = 0; i < extConfigs.size(); i++) {
            String dataId = extConfigs.get(i).getDataId();
            if (dataId == null || dataId.trim().length() == 0) {
                throw new IllegalStateException(String.format(
                        "the [ spring.cloud.nacos.config.ext-config[%s] ] must give a dataId",
                        i));
            }
            dataIds[i] = dataId;
        }
        checkDataIdFileExtension(dataIds);
    }

    private void loadApplicationConfiguration(
            CompositePropertySource compositePropertySource, String dataIdPrefix,
            NacosConfigProperties properties, Environment environment) {

        String fileExtension = properties.getFileExtension();
        String nacosGroup = properties.getGroup();

        // load directly once by default
        loadNacosDataIfPresent(compositePropertySource, dataIdPrefix, nacosGroup,
                fileExtension, true);
        // load with suffix, which have a higher priority than the default
        loadNacosDataIfPresent(compositePropertySource,
                dataIdPrefix + DOT + fileExtension, nacosGroup, fileExtension, true);
        // Loaded with profile, which have a higher priority than the suffix
        for (String profile : environment.getActiveProfiles()) {
            String dataId = dataIdPrefix + SEP1 + profile + DOT + fileExtension;
            loadNacosDataIfPresent(compositePropertySource, dataId, nacosGroup,
                    fileExtension, true);
        }
    }

    private void loadNacosDataIfPresent(final CompositePropertySource composite,
            final String dataId, final String group, String fileExtension,
            boolean isRefreshable) {
        if (null == dataId || dataId.trim().length() < 1) {
            return;
        }
        if (null == group || group.trim().length() < 1) {
            return;
        }
        NacosPropertySource propertySource = this.loadNacosPropertySource(dataId, group,
                fileExtension, isRefreshable);
        this.addFirstPropertySource(composite, propertySource, false);
    }

    private NacosPropertySource loadNacosPropertySource(final String dataId,
            final String group, String fileExtension, boolean isRefreshable) {
        if (NacosContextRefresher.getRefreshCount() != 0) {
            if (!isRefreshable) {
                return NacosPropertySourceRepository.getNacosPropertySource(dataId);
            }
        }
        return nacosPropertySourceBuilder.build(dataId, group, fileExtension,
                isRefreshable);
    }

    /**
     * Add the nacos configuration to the first place and maybe ignore the empty
     * configuration.
     */
    private void addFirstPropertySource(final CompositePropertySource composite,
            NacosPropertySource nacosPropertySource, boolean ignoreEmpty) {
        if (null == nacosPropertySource || null == composite) {
            return;
        }
        if (ignoreEmpty && nacosPropertySource.getSource().isEmpty()) {
            return;
        }
        composite.addFirstPropertySource(nacosPropertySource);
    }

    private static void checkDataIdFileExtension(String[] dataIdArray) {
        if (dataIdArray == null || dataIdArray.length < 1) {
            throw new IllegalStateException("The dataId cannot be empty");
        }
        // Just decide that the current dataId must have a suffix
        NacosDataParserHandler.getInstance().checkDataId(dataIdArray);
    }

    private boolean checkDataIdIsRefreshable(String refreshDataIds, String sharedDataId) {
        if (StringUtils.isEmpty(refreshDataIds)) {
            return false;
        }

        String[] refreshDataIdArray = refreshDataIds.split(SHARED_CONFIG_SEPARATOR_CHAR);
        for (String refreshDataId : refreshDataIdArray) {
            if (refreshDataId.equals(sharedDataId)) {
                return true;
            }
        }

        return false;
    }

}

修改內容核心邏輯:加粗標紅代碼

原理:主要是結合jasypt的實現原理,對nacos生成的propertySource進行包裝,包裝成EncryptableXXX

 

小知識:自己寫的同包路徑下的同名類會覆蓋jar包中的同包路徑下的同名類,比如本文重寫的這些代碼,在程序運行時會覆蓋原來nacos jar包中的代碼而生效。

 

完畢。 

 

為了方便演示,直接在源碼上修改,后續如果升級nacos版本可能會帶來問題,僅為拋磚引玉。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM