1. 概述
Spring Cloud Config是Spring的用戶端/伺服器方法,用于跨多個應用程式和環境存儲和服務分布式配置。
理想情況下,此配置存儲在 Git 版本控制下進行版本控制,并且可以在應用程式運作時進行修改。雖然它非常适合使用所有支援的配置檔案格式以及 Environment、PropertySource 或 @Value 等構造的 Spring 應用程式,但它可以在運作任何程式設計語言的任何環境中使用。
在本文中,我們将重點介紹如何設定 Git 支援的配置伺服器,在簡單的 REST 應用程式伺服器中使用它,以及如何設定包含加密屬性值的安全環境。
2. 項目設定和依賴關系
首先,将建立兩個新的 Maven 項目。伺服器項目依賴于 spring-cloud-config-server 子產品,以及 spring-boot-starter-security 和 spring-boot-starter-web starter 捆綁包:
<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,它通過自動配置注釋@EnableConfigServer引入所有必需的設定:
@SpringBootApplication
@EnableConfigServer
public class ConfigServer {
public static void main(String[] arguments) {
SpringApplication.run(ConfigServer.class, arguments);
}
}
現在需要配置伺服器正在偵聽的伺服器端口和一個Git-url,它提供版本控制的配置内容。後者可以與http,ssh或本地檔案系統上的簡單檔案等協定一起使用。
提示:如果計劃使用指向同一配置存儲庫的多個配置伺服器執行個體,可以将伺服器配置為将存儲庫克隆到本地臨時檔案夾中。但請注意具有雙因素身份驗證的私有存儲庫;他們很難處理!在這種情況下,在本地檔案系統上克隆它們并使用副本會更容易。
還有一些占位符變量和搜尋模式可用于配置存儲庫url;但是,這超出了本文的範圍。如果您有興趣了解更多資訊,官方文檔是一個很好的起點。
還需要在應用程式中為基本身份驗證設定使用者名和密碼屬性以避免在每次應用程式重新啟動時自動生成密碼:
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' 的值,後跟短劃線和活動配置檔案。例如:
gt; git init
gt; echo 'user.role=Developer' > config-client-development.properties
gt; echo 'user.role=User' > config-client-production.properties
gt; git add .
gt; git commit -m 'Initial config-client properties'
故障 排除:如果遇到與 ssh 相關的身份驗證問題,可以在 ssh 伺服器上仔細檢查 ~/.ssh/known_hosts 和 ~/.ssh/authorized_keys。
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中的開發配置檔案下計劃運作的用戶端的配置:
gt; curl http://root:s3cr3t@localhost:8888/config-client/development/master
6. 用戶端實作
接下來,建立用戶端。這将是一個非常簡單的用戶端應用程式,由一個帶有一個 GET 方法的 REST 控制器組成。
要擷取伺服器配置,必須将配置放在 application.properties 檔案中。Spring Boot 2.4引入了一種使用spring.config.import屬性加載配置資料的新方法,該屬性現在是綁定到配置伺服器的預設方式:
@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);
}
}
除了應用程式名稱之外,還需要将active profile和連接配接詳細資訊放在我們的 application.properties 中:
spring.application.name=config-client
spring.profiles.active=development
spring.config.import=optional:configserver:http://root:s3cr3t@localhost:8888
這将在 http://localhost:8888 連接配接到配置伺服器,并且在啟動連接配接時還将使用HTTP basic security。還可以使用使用者名和密碼分别使用spring.cloud.config.username和spring.cloud.config.password屬性。
在某些情況下,如果服務無法連接配接到配置伺服器,可能希望服務啟動失敗。如果這是所需的行為,可以删除optional:字首,以使用戶端因異常而停止。
為了測試配置是否正确地從伺服器接收,并且角色值被注入到控制器方法中,隻需在啟動用戶端後通路/whoami/Mr_Pink:
gt; curl http://localhost:8080/whoami/Mr_Pink
如果響應如下,則Spring Cloud Config伺服器及其用戶端目前工作正常:
Hello! You're Mr_Pink and you'll become a(n) Developer...
7. 加密和解密
要求:要将加密強密鑰與Spring加密和解密功能一起使用,需要在JVM 中安裝“Java 加密擴充 (JCE) 無限強度管轄政策檔案”。要安裝,請按照下載下傳中包含的說明進行操作。一些 Linux 發行版還通過其包管理器提供可安裝的包。
由于配置伺服器支援屬性值的加密和解密,是以我們可以使用公共存儲庫作為敏感資料的存儲,例如使用者名和密碼。加密值以字元串 {cipher} 為字首,如果伺服器配置為使用對稱密鑰或密鑰對,則可以通過對路徑“/encrypt”的REST調用生成。
還可以使用要解密的接口。兩個接口都接受包含應用程式名稱及其目前配置檔案的占位符的路徑:“/*/{name}/{profile}”。這對于控制每個用戶端的加密特别有用。但是,在它們有用之前,我們必須配置一個加密密鑰,将在下一節中執行此操作。
提示:如果使用 curl 來調用 en-/decryption API,最好使用 –data-urlencode 選項(而不是 –data/-d),或者将“Content-Type”标頭顯式設定為“text/plain”。這可確定正确處理加密值中的“+”等特殊字元。
如果在通過用戶端擷取時無法自動解密值,則其密鑰将使用名稱本身重命名,并以單詞“invalid”為字首。這應該會防止使用加密值作為密碼。
提示:在設定包含 YAML 檔案的存儲庫時,我們必須用單引号将加密值和字首值括起來。但是,屬性并非如此。
7.1. CSRF
預設情況下,Spring Security為發送到應用程式的所有請求啟用 CSRF 保護。是以,為了能夠使用 /encrypt 和 /decrypt 接口,讓我們為它們禁用 CSRF:
@Configuration
public class SecurityConfiguration {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf()
.ignoringAntMatchers("/encrypt/**")
.ignoringAntMatchers("/decrypt/**");
//...
}
}
7.2. 密鑰管理
預設情況下,配置伺服器能夠以對稱或非對稱方式加密屬性值。
要使用對稱加密,我們隻需将 application.properties 中的屬性“encrypt.key”設定為我們選擇的密鑰。 或者,我們可以傳入環境變量ENCRYPT_KEY。
對于非對稱加密,我們可以将“encrypt.key”設定為PEM編碼的字元串值或配置要使用的密鑰庫。
由于示範伺服器需要一個高度安全的環境,将選擇後一個選項,同時首先使用 Java keytool 生成新的密鑰庫,包括 RSA 密鑰對:
gt; 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
然後,将建立的密鑰庫添加到伺服器的 application.properties 中并重新運作它:
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
接下來,将查詢加密接口,并将響應作為值添加到存儲庫中的配置中:
gt; export PASSWORD=$(curl -X POST --data-urlencode d3v3L \
http://root:s3cr3t@localhost:8888/encrypt)
gt; echo "user.password={cipher}$PASSWORD" >> config-client-development.properties
gt; git commit -am 'Added encrypted password'
gt; 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);
}
}
最後,針對用戶端的查詢将顯示配置值是否正确解密:
gt; 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. 使用多個密鑰
如果想使用多個密鑰進行加密和解密,例如每個服務的應用程式都有一個專用密鑰,可以在 {cipher} 字首和 BASE64 編碼的屬性值之間以 {name:value} 的形式添加另一個字首。
配置伺服器幾乎可以開箱即用地了解像 {secret:my-crypto-secret} 或 {key:my-key-alias} 這樣的字首。後一個選項需要在我們的 application.properties 中配置密鑰庫。在此密鑰庫中搜尋比對的密鑰别名。例如:
user.password={cipher}{secret:my-499-s3cr3t}AgAMirj1DkQC0WjRv...
user.password={cipher}{key:config-client-key}AgAMirj1DkQC0WjRv...
對于沒有密鑰庫的方案,必須實作類型為 TextEncryptorLocator 的@Bean,該處理查找并為每個鍵傳回一個 TextEncryptor-Object。
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服務發現中可用,并在配置用戶端中啟用自動伺服器發現。