1. 概述
Spring Cloud Config
是 Spring 的客户端/服務器架構,用於在多個應用程序和環境中存儲和提供分佈式配置。此配置存儲庫理想情況下應在 Git 版本控制下進行版本管理,並且可以在運行時通過應用程序進行修改。 儘管它非常適合在 Spring 應用程序中使用所有支持的配置文件格式以及 Environment、PropertySource 或 @Value 這樣的構造,但它也可以在任何使用任何編程語言的任何環境中進行使用。
在本教程中,我們將重點介紹如何設置一個基於 Git 的配置服務器,並在一個簡單的 REST 應用程序服務器中使用它,並設置一個安全的環境,包括加密的屬性值。
2. 項目設置與依賴項
首先,我們將創建兩個新的 Maven項目。 服務器項目依賴於 spring-cloud-config-server模塊,以及 spring-boot-starter-security和spring-boot-starter-web啓動包:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
但是,對於客户端項目,我們只需要 spring-cloud-starter-config和spring-boot-starter-web 模塊:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
3. 一個配置服務器的實現
應用程序的主要部分是一個配置類,更具體地説是一個@SpringBootApplication,它通過auto-configure註解@EnableConfigServer來拉取所有必需的設置。
@SpringBootApplication
@EnableConfigServer
public class ConfigServer {
public static void main(String[] arguments) {
SpringApplication.run(ConfigServer.class, arguments);
}
}
現在我們需要配置服務器監聽的port以及一個Git-url,它提供我們的版本控制的配置內容。 後者可以使用諸如http、ssh或本地文件系統上的簡單file協議。
提示: 如果我們計劃使用指向同一個配置倉庫的多個配置服務器實例,則可以配置服務器克隆我們的倉庫到本地臨時文件夾。 但是請注意帶有雙因素身份驗證的私有倉庫,它們很難處理! 在這種情況下,在本地文件系統中克隆它們並使用副本工作起來更容易。
此外,還有用於配置repository-url的placeholder variables and search patterns可用;但是,這超出了本文的範圍。如果您想了解更多信息,官方文檔是一個好的起點。
我們還需要為Basic-Authentication設置用户名和密碼,以便在每次應用程序重啓時避免自動生成的密碼:
server.port=8888
spring.cloud.config.server.git.uri=ssh://localhost/config-repo
spring.cloud.config.server.git.clone-on-start=true
spring.security.user.name=root
spring.security.user.password=s3cr3t
4. 使用 Git 存儲配置
為了完成我們的服務器,我們需要在配置的 URL 下初始化一個 Git 倉庫,創建一些新的屬性文件,並用一些值填充它們。配置文件的名稱類似於標準的 Spring application.properties,但除了“application”一詞之外,使用配置的名稱,例如屬性 ‘spring.application.name’ 的值,後跟一個連字符和活動配置文件。例如:
$> git init
$> echo 'user.role=Developer' > config-client-development.properties
$> echo 'user.role=User' > config-client-production.properties
$> git add .
$> git commit -m 'Initial config-client properties'
故障排除: 如果我們遇到與 ssh 相關的身份驗證問題,我們可以檢查 ~/.ssh/known_hosts 和 ~/.ssh/authorized_keys 在我們的 ssh 服務器上。
5. 查詢配置
現在我們能夠啓動我們的服務器。服務器提供的基於 Git 的配置 API 可以使用以下路徑進行查詢:
/{application}/{profile}[/{label}]
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties
{label} 佔位符表示一個 Git 分支,{application} 表示客户端的應用程序名稱,{profile} 表示客户端的當前活動應用程序配置文件。
因此,我們可以通過以下方式獲取我們計劃的配置客户端在 master 開發配置文件下運行的配置:
$> curl http://root:s3cr3t@localhost:8888/config-client/development/master
controller with one method.
file. Spring Boot 2.4 introduced a new way to load configuration data using the property, which is now the default way to bind to Config Server:
@SpringBootApplication
@RestController
public class ConfigClient {
@Value("${user.role}")
private String role;
public static void main(String[] args) {
SpringApplication.run(ConfigClient.class, args);
}
@GetMapping(
value = "/whoami/{username}",
produces = MediaType.TEXT_PLAIN_VALUE)
public String whoami(@PathVariable("username") String username) {
return String.format("Hello!
You're %s and you'll become a(n) %s...\n", username, role);
}
}
:
spring.application.name=config-client
spring.profiles.active=development
spring.config.import=optional:configserver:http://root:s3cr3t@localhost:8888
spring.cloud.config.username and spring.cloud.config.password properties, respectively.
prefix to make the client halt with an exception.
gets injected in our controller method, we simply curl it after booting the client:
$> curl http://localhost:8080/whoami/Mr_Pink
and its client are working fine for now:
Hello! You're Mr_Pink and you'll become a(n) Developer...
7. 密碼加密與解密
要求:為了使用強大的加密密鑰與 Spring 加密和解密功能一起使用,我們需要 ‘Java 密碼擴展 (JCE) 無限強度管轄範圍文件’安裝在我們的 JVM 中。這些可以從 Oracle下載。要安裝,請按照下載中包含的説明進行操作。Linux 某些發行版也通過其包管理器提供了一個可安裝的軟件包。由於配置服務器正在支持屬性值的加密和解密,因此我們可以使用公共存儲庫作為敏感數據(如用户名和密碼)的存儲位置。加密值以字符串 {cipher} 開頭,可以通過 REST 調用路徑 ‘/encrypt’生成,如果服務器配置為使用對稱密鑰或密鑰對。
還有一個解密端點可用。這兩個端點都接受包含應用程序名稱和當前配置文件名稱的路徑佔位符的路徑:‘/*/{name}/{profile}’。這對於控制客户端的密碼學非常有用。但是,在使用前,我們必須配置一個加密密鑰,我們將在下一部分中完成。
提示: 如果我們使用 curl 調用 en-/解密 API,最好使用 –data-urlencode 選項(而不是 –data/-d),或者設置 ‘Content-Type’標題為 ‘text/plain’。這確保了正確處理特殊字符,如加密值中的 ‘+’字符。
如果一個值無法在客户端通過檢索時自動解密,則它的 密鑰將使用其名稱重命名,並在名稱前加上 ‘invalid.’。這可以防止使用加密值作為密碼。
提示: 當設置包含 YAML 文件的存儲庫時,我們必須用單引號將加密和前綴的值包圍。但是,這不適用於 Properties。
7.1. CSRF
默認情況下,Spring Security 為應用程序發往我們的應用程序的所有請求啓用 CSRF 保護。因此,為了能夠使用 /encrypt和/decrypt端點,我們應該禁用它們中的 CSRF:
@Configuration
public class SecurityConfiguration {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf(csrf -> csrf.ignoringRequestMatchers(
"/encrypt/**", "/decrypt/**"
))
//...
}
}
7.2. 密鑰管理
默認情況下,配置服務器能夠以對稱或非對稱方式加密屬性值。要使用對稱密碼學,我們只需將 ‘encrypt.key’屬性設置為我們選擇的秘密。 另一種方法是使用環境變量 ENCRYPT_KEY傳遞。
對於非對稱密碼學,我們可以將 ‘encrypt.key’設置為 PEM 編碼的字符串值或配置 keystore以使用。
由於我們需要在演示服務器上創建一個高度安全的環境,因此我們將選擇後者,包括生成一個包含 RSA 密鑰對的 keystore,並使用 Java keytool首先:
$> keytool -genkeypair -alias config-server-key \
-keyalg RSA -keysize 4096 -sigalg SHA512withRSA \
-dname 'CN=Config Server,OU=Spring Cloud,O=Baeldung' \
-keypass my-k34-s3cr3t -keystore config-server.jks \
-storepass my-s70r3-s3cr3t
然後我們將向服務器的應用程序.properties中添加創建的 keystore,並重新運行它:
encrypt.keyStore.location=classpath:/config-server.jks
encrypt.keyStore.password=my-s70r3-s3cr3t
encrypt.keyStore.alias=config-server-key
encrypt.keyStore.secret=my-k34-s3cr3t
接下來,我們將查詢加密端點,並將響應作為配置項添加到存儲庫中:
$> export PASSWORD=$(curl -X POST --data-urlencode d3v3L \
http://root:s3cr3t@localhost:8888/encrypt)
$> echo "user.password={cipher}$PASSWORD" >> config-client-development.properties
$> git commit -am 'Added encrypted password'
$> curl -X POST http://root:s3cr3t@localhost:8888/refresh
為了測試我們的設置是否正常工作,我們將修改 ConfigClient類並重新啓動客户端:
@SpringBootApplication
@RestController
public class ConfigClient {
...
@Value("${user.password}")
private String password;
...
public String whoami(@PathVariable("username") String username) {
return String.format("Hello!
You're %s and you'll become a(n) %s, " +
"but only if your password is '%s'!\n",
username, role, password);
}
}
最後,我們查詢客户端,以查看我們的配置值是否已正確解密:
$> curl http://localhost:8080/whoami/Mr_Pink
Hello! You're Mr_Pink and you'll become a(n) Developer, \
but only if your password is 'd3v3L'!
7.3. 使用多個密鑰
如果我們想使用用於加密和解密的不同密鑰,例如為每個提供的應用程序提供一個,則可以將格式為 {name:value}的另一個前綴添加到 {cipher}前綴和 BASE64編碼的屬性值之間。配置服務器可以理解像 {secret:my-crypto-secret}或 {key:my-key-alias}這樣的前綴,幾乎無需配置。後一種選項需要我們在 application.properties中配置一個 keystore。
user.password={cipher}{secret:my-499-s3cr3t}AgAMirj1DkQC0WjRv...
user.password={cipher}{key:config-client-key}AgAMirj1DkQC0WjRv...
對於沒有 keystore 的情況,我們必須實現一個 @Bean,類型為 TextEncryptorLocator,該對象處理查找並返回 TextEncryptor對象,用於每個密鑰。
7.4. 提供加密屬性
如果我們想禁用服務器端密碼學,並讓客户端處理屬性值的解密,則可以將以下內容放入服務器的 application.properties中:spring.cloud.config.server.encrypt.enabled=false
此外,我們可以刪除所有其他 ‘encrypt.’屬性以禁用 REST端點。
8. 結論
現在我們能夠創建一個配置服務器,從 Git 倉庫提供一組配置文件給客户端應用程序。 此外,我們還可以做一些其他事情。
例如:
- 以 YAML 或 Properties 格式提供配置,而不是 JSON,同時解析佔位符。 這在非 Spring 環境中使用時非常有用,因為配置沒有直接映射到 PropertySource。
- 提供純文本配置文件,可選地帶有解析後的佔位符。 例如,這可以用於提供環境相關的日誌配置。
- 將配置服務器嵌入到應用程序中,其中它從 Git 倉庫配置自身,而不是作為獨立應用程序提供給客户端。 因此,我們必須設置一些屬性,或者必須刪除 @EnableConfigServer 註解,這取決於用例。
- 使配置服務器在 Spring Netflix Eureka 服務發現中可用,並啓用配置客户端的自動服務器發現。 這在服務器沒有固定位置或移動位置時變得重要。