日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

原創文章,轉載請注明出處

背景

最近在忙腳手架升級,為了減少中間件依賴,降低學習成本,將原來使用的Apollo棄用,和服務發現一同使用 Nacos 來實現。

后面公司安全部門做安全檢查,要求對敏感配置增加安全保護,需要實現對部分配置的配置加密。

先說一下版本。

spring-boot-starter-parent: 2.3.11.RELEASE
spring-cloud-starter-alibaba-nacos-discovery: 2.2.6.RELEASE
spring-cloud-starter-alibaba-nacos-config: 2.2.6.RELEASE

查閱Nacos官方文檔,配置加密功能當前未支持,所以只好自己碼。

Nacos Config 支持配置加密解決方案

 

我們的目標如下

  • 考慮到使用和部署的方便性,明文密文配置應同時兼容,在不改動版本的情況下可以自由切換
  • 明文或密文的差別應該在腳手架中封裝,對業務代碼層無影響
  • 加密密鑰可配,生產環境密鑰只由運維掌握

初期嘗試

最開始的嘗試是希望依賴于Spring擴展點對數據做加解密處理,我嘗試了兩個方式

  • EnvironmentPostProcessor
  • PropertySourceLoader

但是經過試驗,兩個擴展點的切入都是在Nacos將配置加載入Context之前,所以并不適用這次的需求。

也考慮到后期使用Nacos配置熱更新的能力,放棄了直接從下層Spring擴展。

starter擴展

Spring擴展失敗,只能從更上層的starter想辦法。

通過代碼定位,可能找到配置加載解析位置是在
com.alibaba.cloud.nacos.client.OvseNacosPropertySourceBuilder的loadNacosData方法中調用com.alibaba.cloud.nacos.parser.NacosDataParserHandler的parseNacosData實現。

我們的目標是盡量不影響后續的版本升級,使用原生包,盡量減少代碼的覆蓋侵入。


spring-cloud-starter-alibaba-nacos-config 中,找到了關鍵的配置文件。

@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "spring.cloud.nacos.config.enabled", matchIfMissing = true)
public class NacosConfigBootstrapConfiguration {

   @Bean
   @ConditionalOnMissingBean
   public NacosConfigProperties nacosConfigProperties() {
      return new NacosConfigProperties();
   }

   @Bean
   @ConditionalOnMissingBean
   public NacosConfigManager nacosConfigManager(
         NacosConfigProperties nacosConfigProperties) {
      return new NacosConfigManager(nacosConfigProperties);
   }

   @Bean
   public NacosPropertySourceLocator nacosPropertySourceLocator(
         NacosConfigManager nacosConfigManager) {
      return new NacosPropertySourceLocator(nacosConfigManager);
   }

}

我們能用的擴展點就是 @ConditionalOnMissingBean 的這兩個 Bean。

我嘗試重寫了NacosConfigManager這個Bean,并在自己腳手架的 spring.factories 中進行了配置,也使用了@Order注解將自定義的Bean置為最高優先級。

但是測試發現由于factories加載順序問題,自定義的配置類還是晚于Nacos自己的配置加載,導致原生的NacosConfigManager仍會被加載。

所以我只能嘗試使用覆蓋原生包配置的方法實現。

定義一個跟原包同名的包 com.alibaba.cloud.nacos,并重寫配置類,這樣會加載到你自定義的配置類。

@Configuration(proxyBeanMethods = false)
public class NacosConfigBootstrapConfiguration {

    @Bean
    public OvseNacosCipherConfigProcessor ovseNacosCipherConfigProcessor(Environment environment) {
        return new OvseNacosCipherConfigProcessor(environment);
    }

    @Bean
    @ConditionalOnMissingBean
    public NacosConfigProperties nacosConfigProperties() {
        return new NacosConfigProperties();
    }

    @Bean
    @ConditionalOnMissingBean
    public NacosConfigManager nacosConfigManager(
            NacosConfigProperties nacosConfigProperties) {
        return new NacosConfigManager(nacosConfigProperties);
    }

    @Bean
    public OvseNacosPropertySourceLocator nacosPropertySourceLocator(
            NacosConfigManager nacosConfigManager, OvseNacosCipherConfigProcessor ovseNacosCipherConfigProcessor) {
        return new OvseNacosPropertySourceLocator(nacosConfigManager, ovseNacosCipherConfigProcessor);
    }

}

可以看出我們注入了一個自己的密文解析器并替換了
NacosPropertySourceLocator。

密文解析器的實現是從環境變量中根據約定的Key提取加密密鑰,我們各環境使用K8s部署,可以方便的管理環境變量和密鑰。

@Slf4j
public class OvseNacosCipherConfigProcessor {

    private boolean secretAvailable;
    private AesEncryptor aesEncryptor;

    public static final String SECRET_ENV_PROP_NAME = "OVSE_ENV_SECRET";

    public static final String CIPHER_PREFIX = "(ovse-cipher-start)";
    public static final String CIPHER_SUFFIX = "(ovse-cipher-end)";

    public OvseNacosCipherConfigProcessor(Environment environment) {

        String secret = environment.getProperty(SECRET_ENV_PROP_NAME);
        this.secretAvailable = StringUtils.isNotBlank(secret);

        if (this.secretAvailable) {
            try {
                this.aesEncryptor = new AesEncryptor(secret);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            log.info("ovse nacos cipher config enable!");
        } else {
            log.warn("ovse nacos cipher config unavailable!");
        }
    }

    public String process(String source) {

        while (source.contains(CIPHER_PREFIX)) {

            int startIndex = source.indexOf(CIPHER_PREFIX);
            int endIndex = source.indexOf(CIPHER_SUFFIX);

            if (startIndex > endIndex) {
                throw new RuntimeException("ovse cipher config end cannot before start: " + source);
            }

            String cipher = source.substring(startIndex + CIPHER_PREFIX.length(), endIndex);
            String plain = cipher2Plain(cipher);
            source = source.substring(0, startIndex) + plain + source.substring(endIndex + CIPHER_SUFFIX.length());
        }

        return source;
    }

    private String cipher2Plain(String cipher) {
        try {
            return this.aesEncryptor.decrypt(cipher);
        } catch (Exception e) {
            throw new RuntimeException("ovse cipher config format error", e);
        }
    }
}

然后重寫了
OvseNacosPropertySourceBuilder和OvseNacosPropertySourceLocator

public class OvseNacosPropertySourceLocator extends NacosPropertySourceLocator {

    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 NacosPropertySourceBuilder nacosPropertySourceBuilder;

    private NacosConfigProperties nacosConfigProperties;

    private NacosConfigManager nacosConfigManager;

    private OvseNacosCipherConfigProcessor ovseNacosCipherConfigProcessor;

    /**
     * recommend to use
     * {@link NacosPropertySourceLocator#NacosPropertySourceLocator(com.alibaba.cloud.nacos.NacosConfigManager)}.
     * @param nacosConfigProperties nacosConfigProperties
     */
    @Deprecated
    public OvseNacosPropertySourceLocator(NacosConfigProperties nacosConfigProperties) {
        super(nacosConfigProperties);
        this.nacosConfigProperties = nacosConfigProperties;
    }

    public OvseNacosPropertySourceLocator(NacosConfigManager nacosConfigManager, OvseNacosCipherConfigProcessor ovseNacosCipherConfigProcessor) {
        super(nacosConfigManager);
        this.nacosConfigManager = nacosConfigManager;
        this.nacosConfigProperties = nacosConfigManager.getNacosConfigProperties();
        this.ovseNacosCipherConfigProcessor = ovseNacosCipherConfigProcessor;
    }

    @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 OvseNacosPropertySourceBuilder(configService,
                timeout, ovseNacosCipherConfigProcessor);
        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;
    }

    /**
     * load shared configuration.
     */
    private void loadSharedConfiguration(
            CompositePropertySource compositePropertySource) {
        List<NacosConfigProperties.Config> sharedConfigs = nacosConfigProperties
                .getSharedConfigs();
        if (!CollectionUtils.isEmpty(sharedConfigs)) {
            checkConfiguration(sharedConfigs, "shared-configs");
            loadNacosConfiguration(compositePropertySource, sharedConfigs);
        }
    }

    /**
     * load extensional configuration.
     */
    private void loadExtConfiguration(CompositePropertySource compositePropertySource) {
        List<NacosConfigProperties.Config> extConfigs = nacosConfigProperties
                .getExtensionConfigs();
        if (!CollectionUtils.isEmpty(extConfigs)) {
            checkConfiguration(extConfigs, "extension-configs");
            loadNacosConfiguration(compositePropertySource, extConfigs);
        }
    }

    /**
     * load configuration of application.
     */
    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 loadNacosConfiguration(final CompositePropertySource composite,
                                        List<NacosConfigProperties.Config> configs) {
        for (NacosConfigProperties.Config config : configs) {
            loadNacosDataIfPresent(composite, config.getDataId(), config.getGroup(),
                    NacosDataParserHandler.getInstance()
                            .getFileExtension(config.getDataId()),
                    config.isRefresh());
        }
    }

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

    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,
                        group);
            }
        }
        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);
    }

    @Override
    public void setNacosConfigManager(NacosConfigManager nacosConfigManager) {
        this.nacosConfigManager = nacosConfigManager;
    }

}
public class OvseNacosPropertySourceBuilder extends NacosPropertySourceBuilder {
    private static final Logger log = LoggerFactory
            .getLogger(NacosPropertySourceBuilder.class);

    private ConfigService configService;

    private long timeout;

    private OvseNacosCipherConfigProcessor ovseNacosCipherConfigProcessor;

    public OvseNacosPropertySourceBuilder(ConfigService configService, long timeout, OvseNacosCipherConfigProcessor ovseNacosCipherConfigProcessor) {
        super(configService, timeout);
        this.configService = configService;
        this.timeout = timeout;
        this.ovseNacosCipherConfigProcessor = ovseNacosCipherConfigProcessor;
    }

    @Override
    public long getTimeout() {
        return timeout;
    }

    @Override
    public void setTimeout(long timeout) {
        this.timeout = timeout;
    }

    @Override
    public ConfigService getConfigService() {
        return configService;
    }

    @Override
    public void setConfigService(ConfigService configService) {
        this.configService = configService;
    }

    /**
     * @param dataId Nacos dataId
     * @param group Nacos group
     */
    @Override
    NacosPropertySource build(String dataId, String group, String fileExtension,
                              boolean isRefreshable) {
        List<PropertySource<?>> propertySources = loadNacosData(dataId, group,
                fileExtension);
        NacosPropertySource nacosPropertySource = new NacosPropertySource(propertySources,
                group, dataId, new Date(), isRefreshable);
        NacosPropertySourceRepository.collectNacosPropertySource(nacosPropertySource);
        return nacosPropertySource;
    }

    private List<PropertySource<?>> loadNacosData(String dataId, String group,
                                                  String fileExtension) {
        String data = null;
        try {
            data = configService.getConfig(dataId, group, timeout);
            if (StringUtils.isEmpty(data)) {
                log.warn(
                        "Ignore the empty nacos configuration and get it based on dataId[{}] & group[{}]",
                        dataId, group);
                return Collections.emptyList();
            }
            if (log.isDebugEnabled()) {
                log.debug(String.format(
                        "Loading nacos data, dataId: '%s', group: '%s', data: %s", dataId,
                        group, data));
            }

            //ovse cipher config process
            data = this.ovseNacosCipherConfigProcessor.process(data);

            return NacosDataParserHandler.getInstance().parseNacosData(dataId, data,
                    fileExtension);
        }
        catch (NacosException e) {
            log.error("get data from Nacos error,dataId:{} ", dataId, e);
        }
        catch (Exception e) {
            log.error("parse data from Nacos error,dataId:{},data:{}", dataId, data, e);
        }
        return Collections.emptyList();
    }
}

測試后發現這個方法實現了需求。

后續我像運維提供了密文配置的生成工具,完成了整套加密配置的處理。

分享到:
標簽:Nacos
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定