欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

關(guān)于springboot配置文件密文解密方式

 更新時間:2022年08月16日 14:54:42   作者:weixin_41106708  
這篇文章主要介紹了關(guān)于springboot配置文件密文解密方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教

在使用 springboot 或者 springcloud 開發(fā)的時候,通常為了保證系統(tǒng)的安全性,配置文件中的密碼等銘感信息都會進行加密處理,然后在系統(tǒng)啟動的時候?qū)γ芪倪M行解密處理。

一、配置文件密文解密

在使用 springboot 或者 springcloud 的時候,通常會在 application.yaml 配置文件中配置數(shù)據(jù)庫的連接信息。

例如:

mysql:
  driver: com.mysql.jdbc.Driver
  url: jdbc:mysql://localhost:3306/test?characterEncoding=utf8
  username: root
  password: 4545222   
  #一般為了信息安全,密碼都會配置成密文的,比如:password: PASSWORD[ 加密后的密文 ]

而在實際的項目中,關(guān)于密碼這一類的銘感信息都是經(jīng)過加密處理的。

例如:

mysql:
  driver: com.mysql.jdbc.Driver
  url: jdbc:mysql://localhost:3306/test?characterEncoding=utf8
  username: root
  # BR23C92223KKDNUIQMPLS0009 為經(jīng)過加密處理的密碼
  password: PASSWORD[BR23C92223KKDNUIQMPLS0009]

經(jīng)過加密的密文密碼在 springboot 項目啟動的時候會被解密成明文,而熟悉 springboot 或是 spring 源碼的同學都知道,不管是 springboot 還是 spring 它們的配置文件在項目啟動后都會被加載到 Environment 對象中,而在 springboot 中,在系統(tǒng)的 Environment 對象創(chuàng)建完成并初始化好了之后,會發(fā)布一個事件:ApplicationEnvironmentPreparedEvent 。

清楚了以上這兩點,那么我們實現(xiàn)配置文件密文解密成對應(yīng)的明文也就有了思路,我們只需要定義一個監(jiān)聽器監(jiān)聽 ApplicationEnvironmentPreparedEvent 事件,當系統(tǒng)的 Environment 對象創(chuàng)建和初始化完成后,會發(fā)布這個事件,然后我們的監(jiān)聽器就能監(jiān)聽到這個事件,最后我們在監(jiān)聽器中找出所有經(jīng)過加密的配置項,然后進行解密,最終再把解密后的明文放入 Environment 對象中。這樣我們就實現(xiàn)了對配置文件中經(jīng)過加密的配置項解密的功能。

代碼如下:

package cn.yjh.listener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.boot.env.OriginTrackedMapPropertySource;
import org.springframework.context.ApplicationListener;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import cn.yjh.util.EncryptUtil;
/**
 * @author YouJinhua
 * @since 2021/9/13 10:21
 */
public class EnvironmentPreparedListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {
    @Override
    public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
        ConfigurableEnvironment env = event.getEnvironment();
        MutablePropertySources pss = env.getPropertySources();
        List<PropertySource> list = new ArrayList<>();
        for(PropertySource ps : pss){
            Map<String,Object>  map = new HashMap<>();
            if(ps instanceof OriginTrackedMapPropertySource){
                OriginTrackedMapPropertySource propertySource = new OriginTrackedMapPropertySource(ps.getName(),map);
                Map<String,Object> src = (Map<String,Object>)ps.getSource();
                src.forEach((k,v)->{
                    String strValue = String.valueOf(v);
                    if(strValue.startsWith("PASSWORD[") && strValue.endsWith("]")) {
                        // 此處進行截取出對應(yīng)的密文 BR23C92223KKDNUIQMPLS0009 ,然后調(diào)用對應(yīng)的解密算法進行解密操作
                        v = EncryptUtil.decrypt("work0", strValue.substring(9, strValue.length()-1));
                    }
                    map.put(k,v);
                });
                list.add(propertySource);
            }
        }
        /** 
            此處是刪除原來的 OriginTrackedMapPropertySource 對象,
            把解密后新生成的放入到 Environment,為什么不直接修改原來的
            OriginTrackedMapPropertySource 對象,此處不做過多解釋
            不懂的可以去看看它對應(yīng)的源碼,也算是留一個懸念,也是希望大家
            能夠沒事多看一看源碼。
        */
        list.forEach(ps->{
            pss.remove(ps.getName());
            pss.addLast(ps);
        });
    }
}

接下來就是如何讓我們的監(jiān)聽器生效了,了解 springboot 自動裝配原理的同學,大家都知道接下來要做什么了,首先在我們的 resources 目錄下新建一個 META-INF 目錄,然后在這個目錄下新建 spring.factories 文件,在文件中加這么一句話:

org.springframework.context.ApplicationListener=cn.yjh.listener.EnvironmentPreparedListener

代碼如下:

# Application Listeners
org.springframework.context.ApplicationListener=cn.yjh.listener.EnvironmentPreparedListener

這樣我們的配置文件密文解密功能就實現(xiàn)了。

二、配置中心密文解密( 以springcloud+nacos為例 )

springcloud + nacos 配置中心的環(huán)境搭建,這里就不做過多的說明了,還不會的小伙伴,可以看看其他的博客

其實不光是我們的配置文件需要加密,從配置中心拉取的配置也是需要加密的。那么從配置中心拉取下來的配置項我們?nèi)绾芜M行解密呢?其實具體的實現(xiàn)思路和配置文件的方式差不多。網(wǎng)上也有對應(yīng)成熟的開源 jar 包(jasypt-spring-boot-starter)可以實現(xiàn)這個功能,這里我不講那種實現(xiàn)方式了,盡管哪種方式使用起來也挺簡單方便的,不會的小伙伴可以看看其他博客或者官方文檔。

我這里講的實現(xiàn)方式是不需要導(dǎo)入任何的jar包的,因為springcloud自己本身都有這方面的實現(xiàn),只是很少人知道,官方文檔講得也比較的難懂。其實當你搭建完springcloud的項目后,你去查看它的jar包依賴,你會發(fā)現(xiàn)默認已經(jīng)導(dǎo)入了一個jar包:

在這里插入圖片描述

這是一個接口,是我們實現(xiàn)解密的關(guān)鍵點,因為當我們的 Environment 對象的數(shù)據(jù)發(fā)生變化時候都會通過事件回調(diào)的機制去調(diào)用這個接口的實現(xiàn)類的decrypt()解密方法,我們先來看一段springcloud的源碼,再來分析我們的實現(xiàn)思路,先看:EncryptionBootstrapConfiguration 的關(guān)鍵源碼:

// 這個注解說明是一個配置類
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ TextEncryptor.class })
@EnableConfigurationProperties({ KeyProperties.class })
public class EncryptionBootstrapConfiguration {
	@Autowired(required = false)
	// 這個地方會從IOC容器中獲取上面我們提到的那個接口的實現(xiàn)類,由于是required = false,所以不一定獲取得到,因為可能容器中沒有這個對象
	private TextEncryptor encryptor;
	@Autowired
	private KeyProperties key;
	
	// 這里 spring IOC 容器添加一個 EnvironmentDecryptApplicationInitializer  組件
	@Bean
	public EnvironmentDecryptApplicationInitializer environmentDecryptApplicationListener() {		
		// 這里判斷上面注入的 TextEncryptor  對象是否為空
		if (this.encryptor == null) {
			//為null,就創(chuàng)建一個默認的
			this.encryptor = new FailsafeTextEncryptor();
		}
		// 否則使用上面注入的那個 TextEncryptor  
		EnvironmentDecryptApplicationInitializer listener = new EnvironmentDecryptApplicationInitializer(
				this.encryptor);
		listener.setFailOnError(this.key.isFailOnError());
		return listener;
	}
	/**
		省略其他代碼,只看關(guān)鍵的
	*/
}

再看這個 EnvironmentDecryptApplicationInitializer 類的源碼:

public class EnvironmentDecryptApplicationInitializer implements
		ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
	/** 
	    這里的 {cipher} 相當于我們 springboot配置文件解密 的 PASSWORD[]
		  springcloud的配置格式是: '{cipher}BR23C92223KKDNUIQMPLS0009'
		  而我們的配置格式是:				PASSWORD[BR23C92223KKDNUIQMPLS0009]
		 	注意: '' 必須要加,不然yaml解析器,解析不了,會報錯。
	*/
	public static final String ENCRYPTED_PROPERTY_PREFIX = "{cipher}";
	// 解密的對象
	private TextEncryptor encryptor;
	// 構(gòu)造函數(shù),傳入解密對象,前一個配置類傳入的
	public EnvironmentDecryptApplicationInitializer(TextEncryptor encryptor) {
		// 進行屬性賦值
		this.encryptor = encryptor;
	}
	
	// 這個方法,我們看關(guān)鍵點
	private void merge(PropertySource<?> source, Map<String, Object> properties) {
		if (source instanceof CompositePropertySource) {
			List<PropertySource<?>> sources = new ArrayList<>(
					((CompositePropertySource) source).getPropertySources());
			Collections.reverse(sources);
			for (PropertySource<?> nested : sources) {
				merge(nested, properties);
			}
		}
		else if (source instanceof EnumerablePropertySource) {
			Map<String, Object> otherCollectionProperties = new LinkedHashMap<>();
			boolean sourceHasDecryptedCollection = false;
			EnumerablePropertySource<?> enumerable = (EnumerablePropertySource<?>) source;
			for (String key : enumerable.getPropertyNames()) {
				Object property = source.getProperty(key);
				if (property != null) {
					String value = property.toString();
					// 這里決定了我們,要使用 {cipher} 開頭,表面我們是一個加密項
					if (value.startsWith(ENCRYPTED_PROPERTY_PREFIX)) {
						// 如何是加密項,放入properties對象中存起來,方便后面解密
						properties.put(key, value);
						if (COLLECTION_PROPERTY.matcher(key).matches()) {
							sourceHasDecryptedCollection = true;
						}
					}
					else if (COLLECTION_PROPERTY.matcher(key).matches()) {
						// put non-encrypted properties so merging of index properties
						// happens correctly
						otherCollectionProperties.put(key, value);
					}
					else {
						// override previously encrypted with non-encrypted property
						properties.remove(key);
					}
				}
			}
			// copy all indexed properties even if not encrypted
			if (sourceHasDecryptedCollection && !otherCollectionProperties.isEmpty()) {
				properties.putAll(otherCollectionProperties);
			}
		}
	}
	private void decrypt(Map<String, Object> properties) {
		properties.replaceAll((key, value) -> {
			String valueString = value.toString();
			if (!valueString.startsWith(ENCRYPTED_PROPERTY_PREFIX)) {
				return value;
			}
			return decrypt(key, valueString);
		});
	}
	
	// 這里是真正調(diào)用解密方法進行解密了
	private String decrypt(String key, String original) {
		String value = original.substring(ENCRYPTED_PROPERTY_PREFIX.length());
		try {
			// 這里的 encryptor 對象就是構(gòu)造函數(shù)傳入的 TextEncryptor 
			value = this.encryptor.decrypt(value);
			if (logger.isDebugEnabled()) {
				logger.debug("Decrypted: key=" + key);
			}
			return value;
		}
		catch (Exception e) {
			String message = "Cannot decrypt: key=" + key;
			if (logger.isDebugEnabled()) {
				logger.warn(message, e);
			}
			else {
				logger.warn(message);
			}
			if (this.failOnError) {
				throw new IllegalStateException(message, e);
			}
			return "";
		}
	}
}

以上兩個類的源碼,我這里省略了很多,想仔細查看的自己可以去看看這兩個類,我這里關(guān)鍵的地方都已經(jīng)做了注釋。

這里給大家梳理一下流程:

  • @Configuration標注EncryptionBootstrapConfiguration 類,說明是個配置類
  • 既然是配置類那么必然是要導(dǎo)入組件到spring中
  • @Autowired 注入TextEncryptor ,默認IOC容器中是沒有的這個對象的,所以注入失敗,值為null
  • TextEncryptor 值為null,就會創(chuàng)建一個默認的 this.encryptor = new FailsafeTextEncryptor();
  • @Bean 導(dǎo)入EnvironmentDecryptApplicationInitializer 這個組件,構(gòu)造函數(shù)傳入 TextEncryptor
  • 接下來就是找到對應(yīng)的加密配置項 if (value.startsWith(ENCRYPTED_PROPERTY_PREFIX))
  • 然后調(diào)用 TextEncryptor接口實現(xiàn)對象的decrypt()方法執(zhí)行解密操作。

通過上面的分析我們知道解密的關(guān)鍵點就是TextEncryptor,如果我們在加載EncryptionBootstrapConfiguration 配置類之前,給IOC容器中加入一個我們自己實現(xiàn)的解密算法,那么等到注入TextEncryptor 的時候,就不會為空了,也就不會創(chuàng)建默認的FailsafeTextEncryptor對象,那么在解密的時候不就執(zhí)行我們自己的解密算法了嗎?

現(xiàn)在的問題就是要解決:

在何時加入,如何加入這個自己實現(xiàn)的解密算法到IOC容器中,這個時候又想到了spring、springboot、springcloud的各種擴展點了,熟悉這些擴展點的都知道

ApplicationPreparedEvent 事件,在 BeanFactory 創(chuàng)建完成后,但是還并沒有執(zhí)行refresh()方法的時候,就會發(fā)布這個事件,因為我們知道解析配置類是屬于refresh()中的一步,所以這樣的思路是可行的。 

實現(xiàn)代碼如下:

package cn.yjh.listener;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.context.event.ApplicationPreparedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.Ordered;
import org.springframework.security.crypto.encrypt.TextEncryptor;
/**
 * @author YouJinhua
 * @since 2021/9/13 9:10
 */
public class RegisterTextEncryptorListener implements ApplicationListener<ApplicationPreparedEvent>, Ordered {
    @Override
    public void onApplicationEvent(ApplicationPreparedEvent event) {
        ConfigurableApplicationContext applicationContext = event.getApplicationContext();
        // 這里回往spring IOC 中添加好幾次,是因為父子容器的原因,所以要判斷一下
        if(applicationContext instanceof AnnotationConfigApplicationContext){
            ConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory();
            // 這里判斷是否已經(jīng)添加過我們自己的解密算法了,沒添加才添加,否則跳過
            if(!beanFactory.containsBean("textEncryptor")){
                beanFactory.registerSingleton("textEncryptor",new TextEncryptor(){
                    @Override
                    public String encrypt(String text) {
                        System.out.println("=====================================加密");
                        return "加密"+text;
                    }
                    @Override
                    public String decrypt(String encryptedText) {
                    	  //這里解密就直接輸出日志,然后直接解密返回
                        System.out.println("=====================================解密");
                        return EncryptUtil.decrypt("work0", encryptedText);
                    }
                });
            }
        }
    }
    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }
}

接下來,就是讓我們的監(jiān)聽器生效了,老規(guī)矩,在spring.factories中加上這么一句話:

org.springframework.context.ApplicationListener=cn.yjh.listener.RegisterTextEncryptorListener

這樣就可以了,注意配置中心配置加密項的時候一定要注意格式,否則解析不了會報錯,正確格式如下:

mysql:
  driver: com.mysql.jdbc.Driver
  url: jdbc:mysql://localhost:3306/test?characterEncoding=utf8
  username: root
  # BR23C92223KKDNUIQMPLS0009 為經(jīng)過加密處理的密碼,注意一定要加 '' 否則解析yaml會報錯  
  password: '{cipher}BR23C92223KKDNUIQMPLS0009'

總結(jié)

springcloud配置中心解密配置項,也是看源碼的時候才發(fā)現(xiàn)原來springcloud已經(jīng)支持了這個功能,以前沒看過這一塊兒的源碼的時候,都不知道可以這么實現(xiàn),以前都是使用:jasypt-spring-boot-starter來實現(xiàn)的,所以說多看源碼還是會有所收獲的,這篇文章就到這里。

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • springboot多模塊多環(huán)境配置文件問題(動態(tài)配置生產(chǎn)和開發(fā)環(huán)境)

    springboot多模塊多環(huán)境配置文件問題(動態(tài)配置生產(chǎn)和開發(fā)環(huán)境)

    這篇文章主要介紹了springboot多模塊多環(huán)境配置文件問題(動態(tài)配置生產(chǎn)和開發(fā)環(huán)境),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-04-04
  • Java編程訪問權(quán)限的控制代碼詳解

    Java編程訪問權(quán)限的控制代碼詳解

    這篇文章主要介紹了Java編程訪問權(quán)限的控制代碼詳解,涉及包名,公共的和私有的等相關(guān)內(nèi)容,分享了相關(guān)代碼示例,小編覺得還是挺不錯的,具有一定借鑒價值,需要的朋友可以參考下
    2018-01-01
  • mybatis-plus阻止全表更新與刪除的實現(xiàn)

    mybatis-plus阻止全表更新與刪除的實現(xiàn)

    BlockAttackInnerInterceptor 是mybatis-plus的一個內(nèi)置攔截器,用于防止惡意的全表更新或刪除操作,本文主要介紹了mybatis-plus阻止全表更新與刪除的實現(xiàn),感興趣的可以了解一下
    2023-12-12
  • Java線程創(chuàng)建靜態(tài)代理模式代碼實例

    Java線程創(chuàng)建靜態(tài)代理模式代碼實例

    這篇文章主要介紹了Java線程創(chuàng)建靜態(tài)代理模式代碼實例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-11-11
  • IntelliJ IDEA 如何徹底刪除項目的步驟

    IntelliJ IDEA 如何徹底刪除項目的步驟

    本篇文章主要介紹了IntelliJ IDEA 如何徹底刪除項目的步驟,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-11-11
  • Spring框架學習之AOP詳解

    Spring框架學習之AOP詳解

    這篇文章主要介紹了Spring框架學習之AOP詳解,文中有非常詳細的代碼示例,對正在學習Spring框架的小伙伴們有一定的幫助,需要的朋友可以參考下
    2021-05-05
  • Spring實現(xiàn)Quartz自動配置的方法詳解

    Spring實現(xiàn)Quartz自動配置的方法詳解

    這篇文章主要介紹了Spring實現(xiàn)Quartz自動配置的方法詳解,如果想在應(yīng)用中使用Quartz任務(wù)調(diào)度功能,可以通過Spring Boot實現(xiàn)Quartz的自動配置,以下介紹如何開啟Quartz自動配置,以及Quartz自動配置的實現(xiàn)過程,需要的朋友可以參考下
    2023-11-11
  • Spring菜鳥教你看源碼沖面試

    Spring菜鳥教你看源碼沖面試

    這篇文章主要介紹了Spring菜鳥教你看源碼沖面試,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-03-03
  • Java實現(xiàn)簡易購物系統(tǒng)

    Java實現(xiàn)簡易購物系統(tǒng)

    這篇文章主要為大家詳細介紹了Java實現(xiàn)簡易購物系統(tǒng),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-05-05
  • 深入理解java動態(tài)代理的兩種實現(xiàn)方式(JDK/Cglib)

    深入理解java動態(tài)代理的兩種實現(xiàn)方式(JDK/Cglib)

    本篇文章主要介紹了java動態(tài)代理的兩種實現(xiàn)方式,詳細的介紹了JDK和Cglib的實現(xiàn)方法,具有一定的參考價值,有興趣的可以了解一下
    2017-04-04

最新評論