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


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

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

jasypt-spring-boot GitHub地址:https://github.com/ulisesbocchio/jasypt-spring-boot

將我們需要加密的配置通過jasypt-spring-boot提供的方法進行加密,然后將加密的內容配置在Apollo中,當項目啟動的時候,jasypt-spring-boot會將Apollo加密的配置進行解密,從而讓使用者獲取到解密之后的內容。

創建一個新的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>

加入下面的依賴信息:

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中,存儲的格式需要按照一定的規則才行:

test.input = ENC(0JK4mrGjPUxkB4XuqEv2YQ==)

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

使用的地方可以直接根據名稱注入配置,比如:

@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微服務 入門 實戰與進階》一書。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM