終於把Apollo存儲加密這件事搞定了

本文摘自於 《Spring Cloud微服務 入門 實戰與進階》 一書。 一些比較重要的配置信息,好比密碼之類的敏感配置,咱們但願將配置加密存儲,保證安全性。Apollo框架自己沒有提供數據加密的功能,若是想要實現數據加密的功能有兩種方式,第一種是改Apollo的源碼,增長加解密的邏輯,第二種比較簡單,基於第三方的框架來對數據進行解密。git

jasypt-spring-boot是一個基於Spring Boot開發的框架,能夠將properties中加密的內容自動解密,在Apollo中也能夠藉助於jasypt-spring-boot這個框架來實現數據的加解密操做。github

jasypt-spring-boot GitHub地址:github.com/ulisesbocch…算法

將咱們須要加密的配置經過jasypt-spring-boot提供的方法進行加密,而後將加密的內容配置在Apollo中,當項目啓動的時候,jasypt-spring-boot會將Apollo加密的配置進行解密,從而讓使用者獲取到解密以後的內容。spring

建立一個新的Maven項目,加入Apollo和jasypt的依賴:數據庫

<dependency>
	  <groupId>com.ctrip.framework.apollo</groupId>
	  <artifactId>apollo-client</artifactId>
	  <version>1.1.0</version>
</dependency>
<!--jasypt加密-->
<dependency>
      <groupId>com.github.ulisesbocchio</groupId>
      <artifactId>jasypt-spring-boot-starter</artifactId>
      <version>1.16</version>
</dependency>
複製代碼

加入下面的依賴信息:bootstrap

server.port=8081
app.id=SampleApp
apollo.meta=http://localhost:8080
apollo.bootstrap.enabled=true
apollo.bootstrap.namespaces=application
jasypt.encryptor.password=yinjihaunkey
複製代碼
  • jasypt.encryptor.password:配置加密的Key

建立一個加密的工具類,用於加密配置:數組

public class EncryptUtil {

    /**
     * 製表符、空格、換行符 PATTERN
     */
    private static Pattern BLANK_PATTERN = Pattern.compile("\\s*|\t|\r|\n");

    /**
     * 加密Key
     */
    private static String PASSWORD = "yinjihaunkey";

    /**
     * 加密算法
     */
    private static String ALGORITHM = "PBEWithMD5AndDES";

    public static Map<String, String> getEncryptedParams(String input) {
        //輸出流
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(1024);
        PrintStream cacheStream = new PrintStream(byteArrayOutputStream);
        
        //更換數據輸出位置
        System.setOut(cacheStream);

        //加密參數組裝
        String[] args = {"input=" + input, "password=" + PASSWORD, "algorithm=" + ALGORITHM};
        JasyptPBEStringEncryptionCLI.main(args);

        //執行加密後的輸出
        String message = byteArrayOutputStream.toString();
        String str = replaceBlank(message);
        int index = str.lastIndexOf("-");

        //返回加密後的數據
        Map<String, String> result = new HashMap<String, String>();
        result.put("input", str.substring(index + 1));
        result.put("password", PASSWORD);
        return result;
    }

    /**
     * 替換製表符、空格、換行符
     *
     * @param str
     * @return
     */
    private static String replaceBlank(String str) {
        String dest = "";
        if (!StringUtils.isEmpty(str)) {
            Matcher matcher = BLANK_PATTERN.matcher(str);
            dest = matcher.replaceAll("");
        }
        return dest;
    }
    
    public static void main(String[] args) {
        System.out.println(getEncryptedParams("hello"));
    }
}
複製代碼

執行main方法,能夠獲得以下輸出:安全

{input=0JK4mrGjPUxkB4XuqEv2YQ==, password=yinjihaunkey}
複製代碼

input就是hello加密以後的內容,將input的值複製存儲到Apollo中,存儲的格式須要按照必定的規則才行:bash

test.input = ENC(0JK4mrGjPUxkB4XuqEv2YQ==)
複製代碼

須要將加密的內容用ENC包起來,這樣jasypt纔會去解密這個值。app

使用的地方能夠直接根據名稱注入配置,好比:

@Value("${test.input}")
private String input;	
複製代碼

input的值就是解密以後的值,使用者不須要關心解密邏輯,jasypt框架在內部處理好了。

jasypt整合Apollo也是有一些不足的地方,目前我只發現了下面幾個問題:

  • 在配置中心修改值後,項目中的值不會刷新

  • 注入Config對象獲取的值沒法解密

@ApolloConfig
private Config config;

@GetMapping("/config/getUserName3")
public String getUserName3() {
     return config.getProperty("test.input", "yinjihuan");
}
複製代碼

上面列舉的2個問題,跟jasypt的實現方式是有關係的,意味着這種加密的方式可能只適合數據庫密碼之類的,啓動時是能夠解密的,並且只是用一次,若是是某些比較核心的業務配置須要加密的話,jasypt是支持不了的,沒法作到實時更新。下章節我會講解如何修改Apollo的源碼來解決這2個問題。

擴展Apollo支持存儲加解密

前面章節中給你們介紹瞭如何使用jasypt爲Apollo中的配置進行加解密操做,基本的需求是可以實現的,但仍是有一些不足的地方。

jasypt只是在啓動的時候將Spring中帶有ENC(xx)這種格式的配置進行解密,當配置發生修改時沒法更新。因爲Apollo框架自己沒有這種對配置加解密的功能,若是咱們想實現加解密,而且可以動態的更新,就須要對Apollo的源碼作一些修改來知足需求。

對源碼修改還須要從新打包,筆者在這邊介紹一個比較簡單的實現方式,就是建立一個跟Apollo框架中如出一轍的類名進行覆蓋,這樣也不用替換已經在使用的客戶端。

若是配置中心存儲的內容是加密的,意味着Apollo客戶端從配置中心拉取下來的配置也是加密以後的,咱們須要在配置拉取下來以後就對配置進行解密,而後再走後面的流程,好比綁定到Spring中。在這個業務點進行切入以後,配置中心加密的內容就能夠自動變成解密後的明文,對使用者透明。

經過分析Apollo的源碼,筆者找到了一個最合適的切入點來作這件事情,這個類就是com.ctrip.framework.apollo.internals.DefaultConfig,DefaultConfig是Coonfig接口的實現類,配置的初始化和獲取都會通過DefaultConfig的處理。

在DefaultConfig內部有一個更新配置的方法updateConfig,能夠在這個方法中對加密的數據進行解密處理:

private void updateConfig(Properties newConfigProperties, ConfigSourceType sourceType) {
	Set<Object> keys = newConfigProperties.keySet();
	for (Object k : keys) {
		String key = k.toString();
		String value = newConfigProperties.getProperty(key);
		// 加密Value
		if (value.startsWith("ENC(") && value.endsWith(")")) {
			logger.debug("加密Value {}", value);
			// 解密而後從新賦值
			try {
				String decryptValue = AesEncryptUtils.aesDecrypt(value.substring(3, value.length()-1), DECRYPT_KEY);
				newConfigProperties.setProperty(key, decryptValue);
			} catch (Exception e) {
				logger.error("加密配置解密失敗", e);
			}
		}
	}
    m_configProperties.set(newConfigProperties);
    m_sourceType = sourceType;
 }
複製代碼

這邊使用了AES來解密,也就是說配置中心的加密內容也須要用相同的加密算法進行加密,至於格式的話仍是用的ENC(xx)這種格式來標識這就是一個加密的配置內容。解密以後將解密的明文內容從新賦值到Properties 中,其餘的流程不變。

建立一個加密測試類,加密配置內容,複製存儲到Apollo中

public class Test {
	public static void main(String[] args) {
		String msg = "hello yinjihaun";
		try {
			String encryptMsg = AesEncryptUtils.aesEncrypt(msg, "1111222233334444");
			System.out.println(encryptMsg);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
複製代碼

輸出內容以下:

Ke4LIPGOp3jCwbIHtmhmBA==

存儲到Apollo中須要用ENC將加密內容包起來,以下:

test.input = ENC(Ke4LIPGOp3jCwbIHtmhmBA==)

仍是用以前的代碼進行測試,Config獲取和Spring注入的方式如能夠成功的獲取到解密的數據,而且在配置中心修改後也能實時推送到客戶端成功解密。

本文摘自於 《Spring Cloud微服務 入門 實戰與進階》 一書。

相關文章
相關標籤/搜索