让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版本可能会带来问题,仅为抛砖引玉。

 

posted @ 2020-03-18 20:36  剑握在手  阅读(9857)  评论(1编辑  收藏  举报
返回顶部↑