本文是Spring Cloud專欄的第十篇文章,了解前九篇文章內容有助於更好的理解本文:
一、介紹
Spring Cloud Config是Spring Cloud團隊創建的一個全新項目,用來為分布式系統中的基礎設施和微服務應用提供集中化的外部配置支持,它分為服務端(config server)與客戶端(config client)兩個部分。其中服務端也稱為分布式配置中心,它是一個獨立的微服務應用,用來連接配置倉庫並為客戶端提供獲取配置信息、加密/解密信息等訪問接口,而客戶端則是微服務架構中的各個微服務應用或基礎設施,它們通過指定的配置中心來管理應用資源與業務相關的配置內容, 並在啟動的時候從配置中心獲取和加載配置信息。Spring Cloud Config實現了對服務端和客戶端中環境變量和屬性配置的抽象映射,所以它除了適用於Spring構建的應用程序之外, 也可以在任何其他語言運行的應用程序中使用。由於Spring Cloud Config實現的配置中心默認采用Git來存儲配置信息,所以使用Spring Cloud Config構建的配置服務器,天然就支持對微服務應用配置信息的版本管理,並且可以通過Git客戶端工具來方便地管理和訪問配置內容。當然它也提供了對其他存儲方式的支持,比如SVN倉庫、本地化文件系統。
二、構建Config Server
1、添加依賴
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency>
2、在啟動類上添加注解@EnableConfigServer
3、在application.yml文件中添加配置
spring: application: name: springcloud-config-server cloud: config: server: git: #配置git倉庫地址 uri: https://gitee.com/coding-farmer/config-center #配置倉庫路徑 search-paths: "{profile}" #訪問git倉庫的用戶名 username: #訪問git倉庫的密碼 password: #配置中心通過git從遠程git庫,有時本地的拷貝被污染, #這時配置中心無法從遠程庫更新本地配置,設置force-pull=true,則強制從遠程庫中更新本地庫 force-pull: true #默認從git倉庫克隆下載的在C:/Users/<當前用戶>/AppData/Local/Temp #basedir: server: port: 8888
如果Git倉庫為公開倉庫,可以不填寫用戶名和密碼,如果是私有倉庫需要填寫,本案例使用的是碼雲公開倉庫。
4、配置中心倉庫目錄結構為:按環境拆分
遠程碼雲倉庫https://gitee.com/coding-farmer/config-center中有3個文件夾,分別是dev,prod,test,里面存放着相應環境的配置文件
5、測試返回數據
http請求地址和資源文件映射如下:
/{application}/{profile}[/{label}]
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties
此時啟動我們的配置中心,通過/{application}/{profile}/[{label}]就能訪問到我們的配置文件了,其中application表示配置文件的名字,對應我們上面的配置文件就是config;profile表示環境,我們有dev、test、prod還有默認,label表示分支,默認我們都是放在master分支上
訪問:http://localhost:8888/configclient/dev,默認分支為master分支
如下圖則證明配置服務中心可以從遠程程序獲取配置信息:
從瀏覽器上可以看到我們放在倉庫中的配置文件信息。JSON中的name表示配置文件名application的部分,profiles表示環境部分,label表示分支,多了一個version,實際上就是我們碼雲上提交信息時產生的版本號,當我們訪問成功后,我們還可以看到控制台打印了如下日志:
INFO 6600 --- [nio-8888-exec-1] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@5c4fca8a: startup date [Thu Aug 15 15:12:07 CST 2019]; root of context hierarchy
INFO 6600 --- [nio-8888-exec-1] o.s.c.c.s.e.NativeEnvironmentRepository : Adding property source: file:/C:/Users/Administrator/AppData/Local/Temp/config-repo-6372945341655107732/dev/configclient-dev.yml
INFO 6600 --- [nio-8888-exec-1] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@5c4fca8a: startup date [Thu Aug 15 15:12:07 CST 2019]; root of context hierarchy
實際上是配置中心通過git clone命令將配置文件在本地保存了一份,這樣可以確保在git倉庫掛掉的時候我們的應用還可以繼續運行,當微服務A/B嘗試去從Config Server中加載配置信息的時候,Config Server會先通過git clone命令克隆一份配置文件保存到本地,此時我們斷掉網絡,再訪問http://localhost:8888/configclient/dev一樣還可以拿到數據,此時的數據就是從本地獲取的。
如果有兩個前綴名相同文件,例如一個configclient.yml,一個configclient-dev.yml。那么在訪問相同前綴的文件時,config-server會對這兩個文件進行一個合並。例如configclient.yml有一段配置是configclient-dev.yml沒有的,理應訪問configclient-dev.yml的時候是沒有那段配置的,但最終的訪問的結果卻是它倆合並之后的內容,即configclient-dev.yml會擁有configclient.yml里所配置的內容。
到此config server構建完成。
三、構建Config Client
Config Client也就是你的微服務應用例如(springcloud-service-consumer、springcloud-service-feign、springcloud-service-provider等等,這些模塊相對於Config Server來說都是Config Client),但是為了保持其他案例模塊的純潔干凈,這里就單獨構建一個Config Client命名為:springcloud-config-client
1、引入依賴
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-client</artifactId> </dependency>
2、新建bootstrap.yml
不了解bootstrap.yml可以參考,SpringCloud入門之常用的配置文件application.yml和 bootstrap.yml區別:https://www.cnblogs.com/BlogNetSpace/p/8469033.html
spring:
application:
name: springcloud-config-client
cloud:
config:
#uri則表示配置中心的地址
uri: http://localhost:8888
#注:config 客戶端在沒有 spring.cloud.config.name屬性的時候,服務端{application} 獲取的是客戶端
#spring.application.name的值,否則,獲取的是 spring.cloud.config.name的值。
#1)、當沒有spring.cloud.config.name時,客戶端獲取的是spring.application.name 所對應的git庫中的文件,並且只能獲取一個文件
#2)、當一個項目中有需求要獲取多個文件時,就需要用到spring.cloud.config.name這個屬性,以逗號分割
name: configclient
profile: dev
#label對應了label部分
label: master
server:
port: 8881
3、編寫Controller
@RestController public class MyController { //配置中心里面配置的env屬性 @Value("${env}") private String env; @RequestMapping("/index") public String env(){ return env; } }
4、訪問http://localhost:8881/index
四、安全保護
開發環境中我們的配置中心肯定是不能隨隨便便被人訪問的,我們可以加上適當的保護機制,由於微服務是構建在Spring Boot之上,所以整合Spring Security是最方便的方式。
1、添加依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
2、默認情況下,我們可以獲得一個名為user的用戶,並且在配置中心啟動的時候,在日志中打印出該用戶的隨機密碼
大多數情況下,我們並不會使用隨機生成密碼的機制,我們可以在配置中心服務端application.yml文件中指定用戶名密碼
spring:
security:
user:
name: coding-farmer
password: 123456
當再次訪問http://localhost:8888/configclient/dev配置中心服務端的時候需要輸入密碼,顯示如下圖
3、由於我們已經為config server設置了安全保護,如果我們這個時候連接到配置中心的客戶端中沒有設置響應對應的安全信息,在獲取配置信息時會返回401錯誤,所以需要通過配置的方式在客戶端中加入安全信息來通過校驗,比如:
spring:
cloud:
config:
username: coding-farmer
password: 123456
五、加密解密
1、對稱加密
在微服務架構中,我們通常會采用DevOps的組織方式來降低因團隊間溝通造成的巨大成本,以加速微服務應用的交付能力。這就使得原本由運維團隊控制的線上信息將交由微服務所屬組織的成員自行維護,其中將會包括大量的敏感信息,比如數據庫的賬戶與密碼等。顯然,如果我們直接將敏感信息以明文的方式存儲於微服務應用的配置文件中是非常危險的。針對這個問題, Spring Cloud Config提供了對屬性進行加密解密的功能,以保護配置文件中的信息安全。比如下面的例子
spring.datasource.username=root
spring.datasource.password={cipher}22fedb745505ffcd1dec962bee3c1f0f3af8a3e6b6930eee9afb8659b16a0c630fd256a181319704b806df90f38e7371
在Spring Cloud Config中通過在屬性值前使用{cipher)前綴來標注該內容是一個加密值,當微服務客戶端加載配置時,配置中心會自動為帶有{cipher}前綴的值進行解密。通過該機制的實現,運維團隊就可以放心地將線上信息的加密資源給到微服務團隊,而不用擔心這些敏感信息遭到泄漏的風險。
1-1、使用前提
在使用 Spring Cloud Config的加密解密功能時,有一個必要的前提需要我們注意。為了啟用該功能,我們需要在配置中心的運行環境中安裝不限長度的JCE版本(Unlimited Strength Java Cryptography Extension)。雖然,JCE功能在JRE中自帶,但是默認使用的是有長度限制的版本。我們可以從 Oracle的官方網站下載到它,它是一個壓縮包,解壓后可以看到下面三個文件:
我們需要將local_policy.jar和US_export_policy.jar兩個文件復制到%JAVA_HOME%\jre\lib\security目錄下,覆蓋原來的默認內容。注意:我這里使用的是JDK8,應該下載對應版本的JCE
1-2、相關端點
在完成了JCE的安裝后,可以嘗試啟動配置中心。在控制台中,將會輸出一些配置中心特有的端點,主要包括如下幾個
-
/encrypt/status:查看加密功能狀態的端點。
-
/key:查看密鑰的端點。
-
/encrypt:對請求的body內容進行加密的端點。
-
/decrypt:對請求的body內容進行解密的端點。
可以嘗試通過GET請求訪問/encrypt/status端點,我們將得到如下內容
該返回信息說明當前配置中心的加密功能還不能使用,因為沒有為加密服務配置對應的密鑰。
1-3、配置秘鑰
我們可以通過encrypt.key屬性在bootstrap.yml(為什么要寫bootstrap.yml文件里查看,關於spring cloud config加密EncryptionTooWeakException異常說明:https://www.iteye.com/blog/357029540-2433259)配置文件中直接指定密鑰信息(對稱性密鑰),比如:
encrypt:
key: coding-farmer
加入上述配置信息后,重啟配置中心,再訪問/encrypt/status端點,我們將得到 如下內容:
此時,我們配置中心的加密解密功能就已經可以使用了,不妨嘗試訪問一下/encrypt 和/decrypt端點來使用加密和解密的功能。注意,這兩個端點都是POST請求,加密和解密信息需要通過請求體來發送。
1-4、使用postman進行加密:http://localhost:8888/encrypt
1-5、使用postman解密:http://localhost:8888/decrypt
1-6、把加密的內容復制到你的配置文件中,提交到倉庫即可
如下圖,加密內容前面要有{cipher}開頭表示該值是一個加密字符,配置中心config-server在獲取到這個值之后會先對值進行解密,解密之后才會返回給客戶端使用,如果是yml文件key=value,value值要加單引號
1-7、訪問:http://localhost:8888/configclient/dev,顯示如下則加密成功
2、非對稱加密
Spring Cloud Config的配置中心不僅可以使用對稱性加密,也可以使用非對稱性加密 (比如RSA密鑰對)。雖然非對稱性加密的密鑰生成與配置相對復雜一些,但是它具有更高的安全性。下面,我們來具體介紹一下如何使用非對稱加密
首先,需要通過keytool工具來生成密鑰對。keytool是JDK中的一個密鑰和證書管理工具。它使用戶能夠管理自己的公鑰/私鑰對及相關證書,用於(通過數字簽名)自我認證(用戶向其他用戶/服務認證自己)或數據完整性以及認證服務。在JDK1.4以后的版本中都包含了這一工具,它的位置在%JAVA_HOME%\bin\keytool.exe。
生成密鑰的具體命令如下所示,在DOS窗口輸入:
另外,如果不想逐步輸入那些提示信息,可以使用-dname來直接指定,而秘鑰庫口令可使用-storepass和-keypass來直接指定。所以,我們可以通過下面命令直接創建與上述命令一樣的秘鑰庫。
keytool -genkeypair -alias config-server -keyalg RSA \
-dname "CN=coding-farmer, OU=company, O=organization, L=city, ST=province,C=china" \
-storepass 123456 \
-keystore config-server.keystore \
-keypass 456789 \
默認情況下,使用上述命令創鍵的秘鑰只有90天有效期,如果想要調整它的有效期,可以通過增加-validity參數來實現,比如我們可以通過下面的命令,讓秘鑰的有效期延長到一年:
keytool -genkeypair -alias config-server -keyalg RSA \
-dname "CN=coding-farmer, OU=company, O=organization, L=city, ST=province, C=china" \
-storepass 123456 \
-keystore config-server.keystore \
-keypass 456789 \
-validity 365 \
上述的三種命令生成方式,最終都會在命令的當前執行目錄下生成一個config-server. keystore文件。下面,我們需要將它保存在配置中心的文件系統中的某個位置, 比如將該文件拷貝到config-server的src\main\resources目錄下,然后在配置中心中加入相關的配置信息
encrypt:
key-store:
location: config-server.keystore
alias: config-server
password: 123456
secret: 456789
非對稱加密的配置信息也可以通過環境變量的方式進行配置,它們對應的具體變量名如下:
ENCRYPT_KEY_STORE_LOCATION
ENCRYPT_KEY_STORE_ALIAS
ENCRYPT_KEY_STORE_PASSWORD
ENCRYPT_KEY_STORE_SECRET
測試方式和對稱加密的測試方式一致
詳細參考案例源碼:https://gitee.com/coding-farmer/springcloud-learn

















