1、概览 {#1概览}
Spring Cloud Config 是 Spring 用于在分布式环境下提供集中式配置的解决方案。
本文将带你了解如何设置一个基于 Git 的配置服务器(包括加密的属性值),以及如何在 REST 应用中使用它。
2、依赖 {#2依赖}
首先是配置服务器应用,包含了 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、实现配置服务器 {#3实现配置服务器}
通过在 @SpringBootApplication
类上注解 @EnableConfigServer
来启用配置服务器。该注解会自动配置所有必要的组件:
@SpringBootApplication
@EnableConfigServer
public class ConfigServer {
public static void main(String[] arguments) {
SpringApplication.run(ConfigServer.class, arguments);
}
}
还需要配置服务器的监听端口和 Git URL(提供版本控制的配置内容)。后者可以使用诸如 http 、ssh 或本地文件系统上的简单文件等协议。
提示:如果计划使用多个配置服务器实例指向同一个配置仓库,可以配置服务器将仓库克隆到本地临时文件夹中。但注意,私有仓库使用了两步验证,处理起来会比较困难!在这种情况下,最好将它们克隆到本地文件系统并使用副本进行操作。
还有一些用于配置 repository-url
的占位变量和搜索模式;不过,这超出了本文的范围。如果你有兴趣了解更多,可以参阅 中文文档。
还需要在 application.properties
中为 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 仓库作为配置存储库 {#4配置-git-仓库作为配置存储库}
在配置的 url 下初始化一个 Git 仓库,创建一些新的 Properties 文件,并在其中填充一些值。
配置文件的名称与普通 application.properties
一样,但使用的不是 application
一词,而是配置名称,如客户端的属性 spring.application.name
的值,后面跟一个破折号和激活的 Profile。
例如:
$> 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 服务器上的 ~/.ssh/known_hosts
和 ~/.ssh/authorized_keys
。
5、查询配置 {#5查询配置}
现在可以启动服务器了。
服务器提供了基于 Git 的配置 API,可以使用以下路径进行查询:
/{application}/{profile}[/{label}]
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties
{label}
占位符指的是 Git 分支,{application}
指的是客户端的应用名称,{profile}
指的是客户端当前活动的 Application Profile。
因此,可以通过以下方式获取在 development Profile 下运行的打算配置客户端在 master
(现在改名为 main
了)分支中的配置:
$> curl http://root:s3cr3t@localhost:8888/config-client/development/master
6、客户端实现 {#6客户端实现}
接下来,处理客户端。这是一个非常简单的客户端应用,有一个带有一个 GET 方法的 REST Controller。
要获取服务器配置,配置必须放置在 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);
}
}
除了应用名称外,还将激活的 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 认证。还可以分别使用 spring.cloud.config.username
和 spring.cloud.config.password
属性设置用户名和密码。
在某些情况下,如果服务无法连接到配置服务器,我们可能希望使其启动失败。可以将 optional:
前缀删除,以使客户端抛出异常并停止运行。
要测试服务器是否正确接收了配置,以及 Controller 方法中是否注入了 role
值,只需在启动客户端后对其进行请求即可:
$> curl http://localhost:8080/whoami/Mr_Pink
如果响应如下,则说明 Spring Cloud Config 服务器及其客户端目前运行正常:
Hello! You're Mr_Pink and you'll become a(n) Developer...
7、加密和解密 {#7加密和解密}
注意 :要使用加密强度高的密钥以及 Spring 加密和解密功能,需要在 JVM 中安装 "Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files"。例如,可以从 Oracle 下载这些文件。要安装,请按照下载中的说明进行操作。一些 Linux 发行版也会通过软件包管理器提供可安装的软件包。
由于配置服务器支持属性值的加密和解密,因此可以使用公共仓库来存储用户名和密码等敏感数据。加密值的前缀是字符串 {cipher}
,如果服务器配置为使用对称密钥或密钥对,则可通过 REST 调用路径 /encrypt
生成加密值。
解密端点也可用。这两个端点都接受包含应用名称及其当前配置文件占位符的路径,即 /{name}/{profile}
: 这对控制每个客户端的加密特别有用。不过,在它们发挥作用之前,必须配置一个加密密钥,这将在下一节中完成。
提示:如果使用 curl 调用加密/解密 API,最好使用 -data-urlencode
选项(而不是 -data/-d
),或者将 Content-Type
头明确设置为 text/plain
。这样可以确保正确处理加密值中的 +
等特殊字符。
如果一个值在通过客户端获取时无法自动解密,其 key
会用名称本身重命名,并在前缀加上 invalid
一词。这样就可以防止将加密值用作密文。
提示:在设置包含 YAML 文件的仓库时,必须用单引号包围加密值和前缀值。但是,Properties 则不同。
7.1、CSRF {#71csrf}
默认情况下,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、Key 管理 {#72key-管理}
默认情况下,配置服务器能以对称或非对称方式加密属性值。
要使用对称加密技术,只需将 application.properties
中的 encrypt.key
属性设置为选择的密钥。或者,也可以通过环境变量 ENCRYPT_KEY
来设置。
对于非对称加密,可以将 encrypt.key
设置为 PEM 编码的字符串值,或者配置一个 keystore
来使用。
本例使用 "非对称加密",它的安全性更高。
首先使用 Java keytool 生成一个新的 Keystore,包括一个 RSA 密钥对:
$> 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
然后,把创建的 Keystore 添加到服务器的 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
接下来,查询加密端点,并将响应作为值添加到仓库中的配置中:
$> 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、使用多个 KEY {#73使用多个-key}
如果想使用多个 KEY 进行加密和解密,例如为每个服务应用设置一个专用 KEY,可以在 {cipher}
前缀和 BASE64 编码属性值之间添加另一个 {name:value}
形式的前缀。
配置服务器几乎可以立即理解 {secret:my-crypto-secret}
或 {key:my-key-alias}
这样的前缀。后一个选项需要在 application.properties
中配置一个 Keystore。该 Keystore 会搜索匹配的密钥别名。例如
user.password={cipher}{secret:my-499-s3cr3t}AgAMirj1DkQC0WjRv...
user.password={cipher}{key:config-client-key}AgAMirj1DkQC0WjRv...
在没有 Keystore 的情况下,必须实现一个 TextEncryptorLocator
类型的 @Bean
来处理查找,并为每个 KEY 返回一个 TextEncryptor
对象。
7.4、提供加密属性 {#74提供加密属性}
如果想禁用服务器端加密,并在本地处理属性值的解密,可以在服务器的 application.properties
中加入以下内容:
spring.cloud.config.server.encrypt.enabled=false
此外,还可以删除所有其他 encrypt.*
属性,禁用 REST 端点。
8、总结 {#8总结}
本文介绍了如何创建一个基于 Git 仓库的配置服务器,以及客户端如何从配置服务器获取配置属性。还介绍了如何对配置属性进行加密和解密。
还可以用配置服务器做其他一些事情。
- 以 YAML 或 Properties 格式提供配置,而不是 JSON 格式,并解析占位符。当在非 Spring 环境中使用时,这将非常有用,因为配置不直接映射到
PropertySource
。 - 按顺序提供纯文本配置文件,可选择解析占位符。例如,这对于提供与环境相关的日志配置非常有用。
- 将配置服务器嵌入到应用中,让它自己从 Git 仓库中进行配置,而不是作为服务客户端的独立应用程序运行。因此,必须设置一些属性和/或移除 @
EnableConfigServer
注解,这取决于使用情况。 - 在 Spring Netflix Eureka 服务发现中提供配置服务器,并在配置客户端中启用自动服务器发现。如果服务器没有固定位置或位置移动,这一点就变得很重要。
Ref:https://www.baeldung.com/spring-cloud-configuration