本文摘自於 《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
複製代碼
建立一個加密的工具類,用於加密配置:數組
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個問題。
前面章節中給你們介紹瞭如何使用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微服務 入門 實戰與進階》 一書。