亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Spring配置文件中密碼明文改為密文處理的通用方式

 更新時(shí)間:2025年01月10日 11:42:14   作者:zyplanke  
SpringBoot和SpringCloud中涉及多個(gè)配置文件,配置文件中對(duì)于密碼默認(rèn)是明文方式,這種方式在生產(chǎn)環(huán)境一般是不被允許的,為避免配置文件中出現(xiàn)明文,應(yīng)當(dāng)在配置文件中配置為密文,然后在啟動(dòng)時(shí)在程序內(nèi)部完成解密,本文提供了通用的處理方式,需要的朋友可以參考下

一、背景

SpringBoot和SpringCloud中涉及多個(gè)配置文件,配置文件中對(duì)于密碼默認(rèn)是明文方式,這種方式在生產(chǎn)環(huán)境一般是不被允許的。為避免配置文件中出現(xiàn)明文,應(yīng)當(dāng)在配置文件中配置為密文,然后在啟動(dòng)時(shí)在程序內(nèi)部完成解密。

本文提供了通用的處理方式,可以適配以下幾類配置文件:

  • 本地bootstrap.properties   在Spring的Bean創(chuàng)建之前的配置
  • 本地application.properties   在Spring的配置,包括帶profile環(huán)境的配置
  • 配置中心上的配置(例如nacos上的Data ID)   

為了適應(yīng)配置文件涉及密碼由明文改為密文,需要分為兩步:

①將配置文件中涉及密文的配置項(xiàng)配置為密文字符串(需自己加密計(jì)算得到);

②在Spring啟動(dòng)中讀取密文字符串并解密還原。

二、思路

對(duì)于以上第②步Spring啟動(dòng)時(shí)的處理,由于以上配置文件在Spring加載的時(shí)機(jī)和生命周期不同,有兩種處理方式:

A) 普通方式

由于Spring中的對(duì)本地application.properties或者配置中心上的配置(例如nacos上的Data ID)在Spring Bean創(chuàng)建過(guò)程中,會(huì)有對(duì)應(yīng)的配置Bean(通過(guò)注解@Configuration申明的Java類),Spring會(huì)自動(dòng)根據(jù)讀取解析配置文件并賦值給Bean。

因此,若需要對(duì)密文字符串并解密還原,可以對(duì)配置Bean(通過(guò)注解@Configuration申明的Java類)進(jìn)行繼承,Override重寫(xiě)對(duì)應(yīng)的set方法,完成解密。

B) 適合bootstrap.properties方式

對(duì)于Spring Cloud,在bootstrap階段還未創(chuàng)建Bean,所以以上Override重寫(xiě)對(duì)應(yīng)的set方法并不適用。所以對(duì)于bootstrap.properties配置文件。可通過(guò)實(shí)現(xiàn)EnvironmentPostProcessor接口,來(lái)捕獲Environment配置,解密后將配置新值設(shè)置到Environment中。

三、示例

A) 普通方式(連接Redis集群)

下面以連接Redis集群為例進(jìn)行說(shuō)明,連接Redis集群的配置項(xiàng)可以在本地application.properties或者配置中心上的配置(例如nacos上的Data ID),且其中spring.redis.password配置項(xiàng)值已經(jīng)設(shè)置為密文。

下面代碼對(duì)配置Bean(通過(guò)注解@Configuration申明的Java類RedisProperties)進(jìn)行繼承,Override重寫(xiě)對(duì)應(yīng)的set方法。Java代碼如下:

package 包指定忽略,請(qǐng)自定;
 
import 忽略解密計(jì)算工具類SystemSecurityAlgorithm,請(qǐng)自定;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.util.StringUtils;
 
/**
 * 連接Redis集群的配置類【通過(guò)@Configuration覆蓋原Bean機(jī)制】:
 *   1、連接Redis的連接password不得出現(xiàn)明文,故需在properties配置文件中配置為加密密文(加密算法Java類為:SystemSecurityAlgorithm),然后在啟動(dòng)時(shí)通過(guò)本類解密
 *   2、貴金屬應(yīng)用服務(wù)采用多數(shù)據(jù)中心DataCenter部署。而每邏輯中心均有獨(dú)立的Redis集群。 應(yīng)用服務(wù)應(yīng)連接同邏輯中心內(nèi)的Redis集群,既北京的應(yīng)用服務(wù)不應(yīng)該連接合肥Redis集群
 *     既:對(duì)于同服務(wù)的不同實(shí)例,應(yīng)根據(jù)服務(wù)實(shí)例所在邏輯中心(具體見(jiàn)枚舉ServiceConstant.DataCenter定義的邏輯中心)連接相同邏輯中心下的Redis集群。
 *     因此:
 *        a).以Spring標(biāo)準(zhǔn)Redis連接配置為基礎(chǔ),對(duì)nodes值中各個(gè)IP端口配置,在各IP前增加一個(gè)大寫(xiě)字母:該IP所在DataCenter數(shù)據(jù)中心的英文代碼
 *        b).以Spring標(biāo)準(zhǔn)Redis連接配置為基礎(chǔ),對(duì)password值改為可配多個(gè)密碼,以逗號(hào)分隔,每個(gè)密碼前增加一個(gè)大寫(xiě)字母,該密碼是連接哪個(gè)Redis集群的DataCenter數(shù)據(jù)中心的英文代碼
 * 為支持以上,定制化開(kāi)發(fā)本類,實(shí)現(xiàn)處理最終還原至Spring標(biāo)準(zhǔn)連接Redis的配置,以供lettuce創(chuàng)建連接池。
 *  -----------------------------------------------------------
 * 機(jī)制適用性:
 * 除了通過(guò)@Configuration覆蓋原Bean機(jī)制,還有通過(guò)實(shí)現(xiàn)EnvironmentPostProcessor接口機(jī)制。兩種機(jī)制適用性說(shuō)明如下:
 *   bootstrap.properties配置文件(bootstrap階段,還未創(chuàng)建Bean) →→適合→→ 【實(shí)現(xiàn)EnvironmentPostProcessor接口機(jī)制】
 *   本地application.properties配置文件(正常SpringBoot啟動(dòng),通過(guò)@Configuration注解的Bean) →→適合→→ 【實(shí)現(xiàn)EnvironmentPostProcessor接口機(jī)制】和【通過(guò)@Configuration覆蓋原Bean機(jī)制】均可
 *   從Nacos等配置中心獲取得到的配置文件 →→適合→→ 【通過(guò)@Configuration覆蓋原Bean機(jī)制】
 *
 */
@Configuration
@Primary // 由于默認(rèn)RedisProperties作為配置類會(huì)自動(dòng)創(chuàng)建Bean。 為避免存在兩個(gè)同類型(RedisProperties)Bean,所以本類通過(guò)注解Primary,使得只有本類生效。相當(dāng)于替代默認(rèn)RedisProperties
public class GjsRedisProperties extends RedisProperties {
 
    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(GjsRedisProperties.class);
 
    @Override
    public void setPassword(String orginPassword) {
        if(StringUtils.hasText(orginPassword)) {
            // 對(duì)密文解密并設(shè)置
            if (StringUtils.hasText(orginPassword) && orginPassword.length() >= 32 ) { // 如果滿足密碼密文的長(zhǎng)度及大小寫(xiě)要求,視為密文,解密
                String padStr = SystemSecurityAlgorithm.decryptStr(orginPassword);
                log.debug("連接Redis配置項(xiàng)spring.redis.password: 解密前orginPassword=[{}], 解密后padStr=[{}]", orginPassword, padStr); //為避免密碼泄露,僅debug才輸出明文
                log.info("連接Redis配置項(xiàng)spring.redis.password: 對(duì)密文orginPassword=[{}]已完成解密", orginPassword);
                super.setPassword(padStr);
            } else { // 不滿足密碼密文的長(zhǎng)度及大小寫(xiě)要求(視為明文,不解密),保持不變
                log.warn("連接Redis配置項(xiàng)spring.redis.password的:orginPassword=[{}]不滿足密碼密文的長(zhǎng)度及大小寫(xiě)要求(視為明文,不解密),保持不變", orginPassword);
                super.setPassword(orginPassword);
            }
        }
    }
}

A) 普通方式(連接RocketMQ)

下面以連接RocketMQ為例進(jìn)行說(shuō)明,連接RocketMQ的配置項(xiàng)可以在本地application.properties或者配置中心上的配置(例如nacos上的Data ID),且其中rocketmq.producer.secret-key和rocketmq.consumer.secret-key配置項(xiàng)值已經(jīng)設(shè)置為密文。

下面代碼對(duì)配置Bean(通過(guò)注解@Configuration申明的Java類RocketMQProperties)進(jìn)行繼承,Override重寫(xiě)對(duì)應(yīng)的set方法。Java代碼如下:

package 包指定忽略,請(qǐng)自定;
 
import 忽略解密計(jì)算工具類SystemSecurityAlgorithm,請(qǐng)自定;
import org.apache.rocketmq.spring.autoconfigure.RocketMQProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.util.StringUtils;
 
import java.util.HashMap;
import java.util.Map;
 
/**
 * 連接RocketMQ的配置類【通過(guò)@Configuration覆蓋原Bean機(jī)制】:
 *   因連接RocketMQ的secret-key不得出現(xiàn)明文,故需在properties配置文件中配置為加密密文(加密算法Java類為:SystemSecurityAlgorithm),然后在啟動(dòng)時(shí)通過(guò)本類解密
 *  -----------------------------------------------------------
 * 機(jī)制適用性:
 * 除了通過(guò)@Configuration覆蓋原Bean機(jī)制,還有通過(guò)實(shí)現(xiàn)EnvironmentPostProcessor接口機(jī)制。兩種機(jī)制適用性說(shuō)明如下:
 *   bootstrap.properties配置文件(bootstrap階段,還未創(chuàng)建Bean) →→適合→→ 【實(shí)現(xiàn)EnvironmentPostProcessor接口機(jī)制】
 *   本地application.properties配置文件(正常SpringBoot啟動(dòng),通過(guò)@Configuration注解的Bean) →→適合→→ 【實(shí)現(xiàn)EnvironmentPostProcessor接口機(jī)制】和【通過(guò)@Configuration覆蓋原Bean機(jī)制】均可
 *   從Nacos等配置中心獲取得到的配置文件 →→適合→→ 【通過(guò)@Configuration覆蓋原Bean機(jī)制】
 *
 */
@Configuration
@Primary // 由于默認(rèn)RocketMQProperties作為配置類會(huì)自動(dòng)創(chuàng)建Bean。 為避免存在兩個(gè)同類型(RocketMQProperties)Bean,所以本類通過(guò)注解Primary,使得只有本類生效。相當(dāng)于替代默認(rèn)RocketMQProperties
public class GjsRocketMQProperties extends RocketMQProperties {
 
    final private String KEYNAME_PRODUCER_SECRET = "rocketmq.producer.secret-key";
    final private String KEYNAME_CONSUMER_SECRET = "rocketmq.consumer.secret-key";
 
    @Autowired
    ConfigurableApplicationContext springContext;
 
    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(GjsRocketMQProperties.class);
 
    @Override
    public void setProducer(Producer producer) {
        final String orginSecretKey = producer.getSecretKey();
        // 對(duì)密文解密并設(shè)置
        if (StringUtils.hasText(orginSecretKey) && orginSecretKey.length() >= 32) { // 如果滿足密碼密文的長(zhǎng)度及大小寫(xiě)要求,視為密文,解密
            String padStr = SystemSecurityAlgorithm.decryptStr(orginSecretKey);
            log.debug("連接RocketMQ配置項(xiàng){}: 解密前orginSecretKey=[{}], 解密后padStr=[{}]", KEYNAME_PRODUCER_SECRET, orginSecretKey, padStr); //為避免密碼泄露,僅debug才輸出明文
            log.info("連接RocketMQ配置項(xiàng){}: 對(duì)密文orginSecretKey=[{}]已完成解密", KEYNAME_PRODUCER_SECRET, orginSecretKey);
            producer.setSecretKey(padStr);
 
            // 由于RocketMQ在構(gòu)建DefaultRocketMQListenerContainer過(guò)程中,會(huì)從Spring的Environment中獲取配置。
            // 附調(diào)用關(guān)系簡(jiǎn)要說(shuō)明如下:
            //     org.apache.rocketmq.spring.support.DefaultRocketMQListenerContainer.afterPropertiesSet()
            //       org.apache.rocketmq.spring.support.DefaultRocketMQListenerContainer.initRocketMQPushConsumer()
            //         org.apache.rocketmq.spring.support.RocketMQUtil.getRPCHookByAkSk()
            //           org.springframework.core.env.AbstractEnvironment.resolveRequiredPlaceholders()
            //             ......
            //               org.springframework.boot.context.properties.source.ConfigurationPropertySourcesPropertyResolver.findPropertyValue()
            // 因此一并修改環(huán)境中的值,使其能取得新值
            modifyEnvironmentValue(springContext.getEnvironment(), KEYNAME_PRODUCER_SECRET, padStr);
 
        } else { // 不滿足密碼密文的長(zhǎng)度及大小寫(xiě)要求(視為明文,不解密),保持不變
            log.warn("連接RocketMQ配置項(xiàng)rocketmq.producer.secret-key值=[{}]不滿足密碼密文的長(zhǎng)度及大小寫(xiě)要求(視為明文,不解密),保持不變", orginSecretKey);
        }
 
        super.setProducer(producer);
    }
 
    @Override
    public void setConsumer(PushConsumer pushConsumer) {
        final String orginSecretKey = pushConsumer.getSecretKey();
        // 對(duì)密文解密并設(shè)置
        if (StringUtils.hasText(orginSecretKey) && orginSecretKey.length() >= 32 ) { // 如果滿足密碼密文的長(zhǎng)度及大小寫(xiě)要求,視為密文,解密
            String padStr = SystemSecurityAlgorithm.decryptStr(orginSecretKey);
            log.debug("連接RocketMQ配置項(xiàng){}: 解密前orginSecretKey=[{}], 解密后padStr=[{}]", KEYNAME_CONSUMER_SECRET, orginSecretKey, padStr); //為避免密碼泄露,僅debug才輸出明文
            log.info("連接RocketMQ配置項(xiàng){}: 對(duì)密文orginSecretKey=[{}]已完成解密", KEYNAME_CONSUMER_SECRET, orginSecretKey);
            pushConsumer.setSecretKey(padStr);
 
            // 由于RocketMQ在構(gòu)建DefaultRocketMQListenerContainer過(guò)程中,會(huì)從Spring的Environment中獲取配置。
            // 附調(diào)用關(guān)系簡(jiǎn)要說(shuō)明如下:
            //     org.apache.rocketmq.spring.support.DefaultRocketMQListenerContainer.afterPropertiesSet()
            //       org.apache.rocketmq.spring.support.DefaultRocketMQListenerContainer.initRocketMQPushConsumer()
            //         org.apache.rocketmq.spring.support.RocketMQUtil.getRPCHookByAkSk()
            //           org.springframework.core.env.AbstractEnvironment.resolveRequiredPlaceholders()
            //             ......
            //               org.springframework.boot.context.properties.source.ConfigurationPropertySourcesPropertyResolver.findPropertyValue()
            // 因此一并修改環(huán)境中的值,使其能取得新值
            modifyEnvironmentValue(springContext.getEnvironment(), KEYNAME_CONSUMER_SECRET, padStr);
 
        } else { // 不滿足密碼密文的長(zhǎng)度及大小寫(xiě)要求(視為明文,不解密),保持不變
            log.warn("連接RocketMQ配置項(xiàng){}的值=[{}]不滿足密碼密文的長(zhǎng)度及大小寫(xiě)要求(視為明文,不解密),保持不變", KEYNAME_CONSUMER_SECRET, orginSecretKey);
        }
 
        super.setConsumer(pushConsumer);
    }
 
    @Override
    public void setPullConsumer(PullConsumer pullConsumer) {
        final String orginSecretKey = pullConsumer.getSecretKey();
        // 對(duì)密文解密并設(shè)置
        if (StringUtils.hasText(orginSecretKey) && orginSecretKey.length() >= 32 ) { // 如果滿足密碼密文的長(zhǎng)度及大小寫(xiě)要求,視為密文,解密
            String padStr = SystemSecurityAlgorithm.decryptStr(orginSecretKey);
            log.debug("連接RocketMQ配置項(xiàng){}: 解密前orginSecretKey=[{}], 解密后padStr=[{}]", KEYNAME_CONSUMER_SECRET, orginSecretKey, padStr); //為避免密碼泄露,僅debug才輸出明文
            log.info("連接RocketMQ配置項(xiàng){}: 對(duì)密文orginSecretKey=[{}]已完成解密", KEYNAME_CONSUMER_SECRET, orginSecretKey);
            pullConsumer.setSecretKey(padStr);
 
            // 由于RocketMQ在構(gòu)建DefaultRocketMQListenerContainer過(guò)程中,會(huì)從Spring的Environment中獲取配置。
            // 附調(diào)用關(guān)系簡(jiǎn)要說(shuō)明如下:
            //     org.apache.rocketmq.spring.support.DefaultRocketMQListenerContainer.afterPropertiesSet()
            //       org.apache.rocketmq.spring.support.DefaultRocketMQListenerContainer.initRocketMQPushConsumer()
            //         org.apache.rocketmq.spring.support.RocketMQUtil.getRPCHookByAkSk()
            //           org.springframework.core.env.AbstractEnvironment.resolveRequiredPlaceholders()
            //             ......
            //               org.springframework.boot.context.properties.source.ConfigurationPropertySourcesPropertyResolver.findPropertyValue()
            // 因此一并修改環(huán)境中的值,使其能取得新值
            modifyEnvironmentValue(springContext.getEnvironment(), KEYNAME_CONSUMER_SECRET, padStr);
 
        } else { // 不滿足密碼密文的長(zhǎng)度及大小寫(xiě)要求(視為明文,不解密),保持不變
            log.warn("連接RocketMQ配置項(xiàng){}的值=[{}]不滿足密碼密文的長(zhǎng)度及大小寫(xiě)要求(視為明文,不解密),保持不變", KEYNAME_CONSUMER_SECRET, orginSecretKey);
        }
 
        super.setPullConsumer(pullConsumer);
    }
 
 
    /**
     * 對(duì)Spring的Environment的配置項(xiàng)的值修改為新值
     * @param environment Spring的Environment對(duì)象
     * @param keyName 配置項(xiàng)名
     * @param newValue 新值
     */
    private void modifyEnvironmentValue(ConfigurableEnvironment environment, final String keyName, String newValue) {
        if(!environment.containsProperty(keyName)) {
            log.warn("當(dāng)前Spring的environment中不存在名為{}的配置項(xiàng)", keyName);
            return;
        }
        if(environment.getProperty(keyName, "").equals(newValue)) {
            log.debug("當(dāng)前Spring的environment中配置項(xiàng){}的值已與新值相同,無(wú)需修改", keyName);
            return;
        }
        Map<String, Object> map = new HashMap<>(); //用于存放新值
        map.put(keyName, newValue);
        // 若有map有值,則把該map作為PropertySource加入列表中,以實(shí)現(xiàn):把environment中對(duì)應(yīng)key的value覆蓋為新值
        // 必須加到First并且不能存在兩個(gè)相同的Name的MapPropertySource,值覆蓋才能生效
        environment.getPropertySources().addFirst(new MapPropertySource("modifyEnvironmentValue-"+keyName, map));
        log.info("已對(duì)Spring的Environment的配置項(xiàng){}的值修改為新值", keyName);
    }
}

B) 適合bootstrap.properties方式

下面以連接Nacos配置中心為例進(jìn)行說(shuō)明,需要在本地bootstrap.properties配置文件中指定連接Nacos配置中心的Nacos用戶名、密碼、服務(wù)端地址、Data ID等信息。bootstrap.properties配置文件有關(guān)連接Nacos配置中心類似如下:

#Nacos配置中心及注冊(cè)中心的authenticate鑒權(quán)用戶名和密碼(需Nacos服務(wù)端開(kāi)啟auth鑒權(quán))
spring.cloud.nacos.username=nacos
spring.cloud.nacos.password=760dee29f9fc82af0cc1d6074879dc39
#Nacos配置中心服務(wù)端的地址和端口(形式ip:port,ip:port,...) 。注:nacos-client1.x會(huì)按順序選其中地址進(jìn)行連接(前個(gè)連接失敗則自動(dòng)選后一個(gè))。nacos-client2.x會(huì)隨機(jī)選其中地址進(jìn)行連接(若連接失敗則自動(dòng)另選)
spring.cloud.nacos.config.server-addr=ip1:8848,ip2:8848,ip3:8848,ip4:8848
 
#Data ID的前綴(如果不設(shè)置,則默認(rèn)取 ${spring.application.name})
#spring.cloud.nacos.config.prefix=
#默認(rèn)指定為開(kāi)發(fā)環(huán)境
#spring.profiles.active=
#Nacos命名空間,此處不設(shè)置,保持默認(rèn)
#spring.cloud.nacos.config.namespace=
#配置組(如果不設(shè)置,則默認(rèn)為DEFAULT_GROUP)
spring.cloud.nacos.config.group=G_CONFIG_GJS_SERVICE
#指定文件后綴(如果不設(shè)置,則默認(rèn)為properties)
spring.cloud.nacos.config.file-extension=properties
 
#以下為全局Data ID
spring.cloud.nacos.config.shared-configs[0].data-id=NacosRegDiscoveryInfo.properties
spring.cloud.nacos.config.shared-configs[0].group=G_CONFIG_GJS_GLOBALSHARED
spring.cloud.nacos.config.shared-configs[0].refresh=true
 
spring.cloud.nacos.config.shared-configs[1].data-id=XXXXX.properties
spring.cloud.nacos.config.shared-configs[1].group=G_CONFIG_GJS_GLOBALSHARED
spring.cloud.nacos.config.shared-configs[1].refresh=true
 
spring.cloud.nacos.config.shared-configs[2].data-id=YYYYY.properties
spring.cloud.nacos.config.shared-configs[2].group=G_CONFIG_GJS_GLOBALSHARED
spring.cloud.nacos.config.shared-configs[2].refresh=true

其中spring.cloud.nacos.password配置項(xiàng)值已經(jīng)設(shè)置為密文。

下面的代碼通過(guò)實(shí)現(xiàn)EnvironmentPostProcessor接口,來(lái)捕獲配置,并將配置新值設(shè)置到Environment中。Java代碼如下:

package 包指定忽略,請(qǐng)自定;
 
import 忽略解密計(jì)算工具類SystemSecurityAlgorithm,請(qǐng)自定;
import org.apache.commons.logging.Log;
import org.springframework.boot.ConfigurableBootstrapContext;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.boot.logging.DeferredLogFactory;
import org.springframework.core.Ordered;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.util.StringUtils;
 
import java.util.HashMap;
import java.util.Map;
 
/**
 * 本類通過(guò)實(shí)現(xiàn)EnvironmentPostProcessor接口,實(shí)現(xiàn)在Spring啟動(dòng)過(guò)程中從environment中讀取指定的key值,處理后,然后把environment中對(duì)應(yīng)key的value覆蓋為新值。
 * 通過(guò)本類已經(jīng)實(shí)現(xiàn)對(duì)bootstrap階段的配置文件處理:
 *   因連接Nacos的password不得出現(xiàn)明文,故bootstrap配置文件中為加密密文(加密算法Java類為:SystemSecurityAlgorithm),然后在啟動(dòng)時(shí)通過(guò)本類解密
 * -----------------------------------------------------------
 * 注意:
 *   a) 需要在META-INF下的spring.factories文件中配置本類后,本類才會(huì)生效(才被Spring掃描識(shí)別到)
 *   b) 因?yàn)楸绢愂峭ㄟ^(guò)實(shí)現(xiàn)EnvironmentPostProcessor接口方式,所以本類在SpringCloud啟動(dòng)過(guò)程中會(huì)被調(diào)用兩次:
 *         首先是在bootstrap配置文件加載后(SpringCloud為支持配置中心的bootstrap階段)
 *         其次是在application配置文件加載后(SpringBoot的正常啟動(dòng)時(shí)加載配置文件階段)
 * 機(jī)制適用性:
 * 除了通過(guò)實(shí)現(xiàn)EnvironmentPostProcessor接口機(jī)制,還有通過(guò)@Configuration覆蓋原Bean機(jī)制。兩種機(jī)制適用性說(shuō)明如下:
 *   bootstrap.properties配置文件(bootstrap階段,還未創(chuàng)建Bean) →→適合→→ 【實(shí)現(xiàn)EnvironmentPostProcessor接口機(jī)制】
 *   本地application.properties配置文件(正常SpringBoot啟動(dòng),通過(guò)@Configuration注解的Bean) →→適合→→ 【實(shí)現(xiàn)EnvironmentPostProcessor接口機(jī)制】和【通過(guò)@Configuration覆蓋原Bean機(jī)制】均可
 *   從Nacos等配置中心獲取得到的配置文件 →→適合→→ 【通過(guò)@Configuration覆蓋原Bean機(jī)制】
 *
 */
public class GjsEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {
 
    /**
     * The default order for the processor.  值越小,優(yōu)先級(jí)越高
     * 因bootstrap配置文件是通過(guò){@link org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor}完成加載處理
     * 由于本EnvironmentPostProcessor類需等待SpringCloud對(duì)bootstrap配置文件后才能執(zhí)行,所以本EnvironmentPostProcessor類優(yōu)先級(jí)需更低
     */
    public static final int ORDER = Ordered.HIGHEST_PRECEDENCE + 50;
 
    private final DeferredLogFactory logFactory;
 
    private final Log logger;
 
    public GjsEnvironmentPostProcessor(DeferredLogFactory logFactory,
                                       ConfigurableBootstrapContext bootstrapContext) {
        this.logFactory = logFactory;
        this.logger = logFactory.getLog(getClass());
    }
 
 
    @Override
    public int getOrder() {
        return ORDER;
    }
 
 
    /**
     * 從environment中讀取指定的key,并進(jìn)行解密,解密后的結(jié)果放入map對(duì)象中
     * @param environment 已經(jīng)有的Spring環(huán)境
     * @param keyName 指定的key名
     * @param map 若完成解密,則將解密后的結(jié)果放入map對(duì)象
     */
    private void decodePwd(ConfigurableEnvironment environment, String keyName, Map<String, Object> map ) {
        if(!environment.containsProperty(keyName)) {
            this.logger.debug("EnvironmentPostProcessor 當(dāng)前Spring的environment中不存在名為"+keyName+"的配置項(xiàng)");
            return;
        }
 
        final String origalValue = environment.getProperty(keyName);
        // 對(duì)密文解密并設(shè)置
        if (StringUtils.hasText(origalValue) && origalValue.length() >= 32) { // 如果滿足密碼密文的長(zhǎng)度及大小寫(xiě)要求,視為密文,解密
            String padStr = SystemSecurityAlgorithm.decryptStr(origalValue);
            this.logger.debug("EnvironmentPostProcessor 配置項(xiàng)"+keyName+"原值=["+origalValue+"], 解密后值=["+padStr+"]"); //為避免在日志中密碼泄露,僅debug才輸出明文
            this.logger.info("EnvironmentPostProcessor 配置項(xiàng)"+keyName+"原值=["+origalValue+"]已完成解密");
            map.put(keyName, padStr);
        }else {
            this.logger.warn("EnvironmentPostProcessor 配置項(xiàng)"+keyName+"值=["+origalValue+"]不滿足密碼密文的長(zhǎng)度及大小寫(xiě)要求(視為明文,不解密),保持不變");
        }
    }
 
    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
        this.logger.debug("EnvironmentPostProcessor before PropertySources size=" + environment.getPropertySources().size());
        this.logger.debug("EnvironmentPostProcessor before PropertySources : " + environment.getPropertySources());
        Map<String, Object> map = new HashMap<>(); //用于存放新值
 
        decodePwd(environment, "spring.cloud.nacos.password", map);
 
        if(!map.isEmpty()) {
            // 若有map有值,則把該map作為PropertySource加入列表中,以實(shí)現(xiàn):把environment中對(duì)應(yīng)key的value覆蓋為新值
            // 必須加到First并且不能存在兩個(gè)相同的Name的MapPropertySource,值覆蓋才能生效
            environment.getPropertySources().addFirst(new MapPropertySource("afterDecodePassword", map));
        }
        this.logger.debug("EnvironmentPostProcessor after PropertySources size=" + environment.getPropertySources().size());
        this.logger.debug("EnvironmentPostProcessor after PropertySources : " + environment.getPropertySources());
    }
 
}

四、總結(jié)

通過(guò)以上兩種方式,可解決Spring各類配置文件對(duì)配置密文的適配和處理。

同時(shí)不僅僅用于密文,凡是需對(duì)配置文件的內(nèi)容在啟動(dòng)時(shí)進(jìn)行改變情況都可以按以上方式進(jìn)行處理。例如啟動(dòng)時(shí)對(duì)配置項(xiàng)值中多個(gè)IP進(jìn)行動(dòng)態(tài)使用等情形。

以上就是Spring配置文件中密碼明文改為密文處理的通用方式的詳細(xì)內(nèi)容,更多關(guān)于Spring密碼明文改為密文處理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 如何在SpringBoot 中使用 Druid 數(shù)據(jù)庫(kù)連接池

    如何在SpringBoot 中使用 Druid 數(shù)據(jù)庫(kù)連接池

    這篇文章主要介紹了SpringBoot 中使用 Druid 數(shù)據(jù)庫(kù)連接池的實(shí)現(xiàn)步驟,幫助大家更好的理解和學(xué)習(xí)使用SpringBoot,感興趣的朋友可以了解下
    2021-03-03
  • 優(yōu)雅地在Java應(yīng)用中實(shí)現(xiàn)全局枚舉處理的方法

    優(yōu)雅地在Java應(yīng)用中實(shí)現(xiàn)全局枚舉處理的方法

    這篇文章主要給大家介紹了關(guān)于如何優(yōu)雅地在Java應(yīng)用中實(shí)現(xiàn)全局枚舉處理的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-02-02
  • Spring整合redis的操作代碼

    Spring整合redis的操作代碼

    這篇文章主要介紹了Spring整合redis的操作代碼,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2022-02-02
  • Java掩碼的幾種使用例舉

    Java掩碼的幾種使用例舉

    今天小編就為大家分享一篇關(guān)于Java掩碼的使用,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧
    2019-03-03
  • Java的ArrayList擴(kuò)容源碼解析

    Java的ArrayList擴(kuò)容源碼解析

    這篇文章主要介紹了Java的ArrayList擴(kuò)容源碼解析,通過(guò)動(dòng)態(tài)擴(kuò)容,ArrayList能夠在添加元素時(shí)保持高效的性能,擴(kuò)容操作是有一定開(kāi)銷的,但由于擴(kuò)容的時(shí)間復(fù)雜度為O(n),其中n是當(dāng)前元素個(gè)數(shù),所以平均情況下,每次添加元素的時(shí)間復(fù)雜度仍然是O(1),需要的朋友可以參考下
    2024-01-01
  • 用Java設(shè)計(jì)模式中的觀察者模式開(kāi)發(fā)微信公眾號(hào)的例子

    用Java設(shè)計(jì)模式中的觀察者模式開(kāi)發(fā)微信公眾號(hào)的例子

    這篇文章主要介紹了用Java設(shè)計(jì)模式中的觀察者模式開(kāi)發(fā)微信公眾號(hào)的例子,這里Java的微信SDK等部分便不再詳述,只注重關(guān)鍵部分和開(kāi)發(fā)過(guò)程中觀察者模式優(yōu)點(diǎn)的體現(xiàn),需要的朋友可以參考下
    2016-02-02
  • 將java項(xiàng)目打包成exe可執(zhí)行文件的完整步驟

    將java項(xiàng)目打包成exe可執(zhí)行文件的完整步驟

    最近項(xiàng)目要求,需要將java項(xiàng)目生成exe文件,下面這篇文章主要給大家介紹了關(guān)于如何將java項(xiàng)目打包成exe可執(zhí)行文件的相關(guān)資料,文章通過(guò)圖文介紹的非常詳細(xì),需要的朋友可以參考下
    2022-06-06
  • eclipse配置javap命令的方法

    eclipse配置javap命令的方法

    本篇文章主要介紹了如何為eclipse配置javap命令,在配置過(guò)程中會(huì)出現(xiàn)的小問(wèn)題的解決方法,非常實(shí)用,需要的朋友可以參考下
    2015-07-07
  • 流讀取導(dǎo)致StringBuilder.toString()亂碼的問(wèn)題及解決

    流讀取導(dǎo)致StringBuilder.toString()亂碼的問(wèn)題及解決

    這篇文章主要介紹了流讀取導(dǎo)致StringBuilder.toString()亂碼的問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-11-11
  • JVM工作原理和工作流程簡(jiǎn)述

    JVM工作原理和工作流程簡(jiǎn)述

    這篇文章主要介紹了關(guān)于JVM工作原理簡(jiǎn)述,主要弄清楚jvm運(yùn)行的來(lái)龍去脈,感興趣的可以一起來(lái)了解一下
    2020-07-07

最新評(píng)論