Spring Cloud Config 快速入门

1、概览

Spring Cloud Config 是 Spring 用于在分布式环境下提供集中式配置的解决方案。

本文将带你了解如何设置一个基于 Git 的配置服务器(包括加密的属性值),以及如何在 REST 应用中使用它。

2、依赖

首先是配置服务器应用,包含了 spring-cloud-config-serverspring-boot-starter-securityspring-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-configspring-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(提供版本控制的配置内容)。后者可以使用诸如 httpssh 或本地文件系统上的简单文件等协议。

提示:如果计划使用多个配置服务器实例指向同一个配置仓库,可以配置服务器将仓库克隆到本地临时文件夹中。但注意,私有仓库使用了两步验证,处理起来会比较困难!在这种情况下,最好将它们克隆到本地文件系统并使用副本进行操作。

还有一些用于配置 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 仓库作为配置存储库

在配置的 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、查询配置

现在可以启动服务器了。

服务器提供了基于 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、客户端实现

接下来,处理客户端。这是一个非常简单的客户端应用,有一个带有一个 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.usernamespring.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、加密和解密

注意:要使用加密强度高的密钥以及 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

默认情况下,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 管理

默认情况下,配置服务器能以对称或非对称方式加密属性值。

要使用对称加密技术,只需将 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

如果想使用多个 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、提供加密属性

如果想禁用服务器端加密,并在本地处理属性值的解密,可以在服务器的 application.properties 中加入以下内容:

spring.cloud.config.server.encrypt.enabled=false

此外,还可以删除所有其他 encrypt.* 属性,禁用 REST 端点。

8、总结

本文介绍了如何创建一个基于 Git 仓库的配置服务器,以及客户端如何从配置服务器获取配置属性。还介绍了如何对配置属性进行加密和解密。

还可以用配置服务器做其他一些事情。

  • 以 YAML 或 Properties 格式提供配置,而不是 JSON 格式,并解析占位符。当在非 Spring 环境中使用时,这将非常有用,因为配置不直接映射到 PropertySource
  • 按顺序提供纯文本配置文件,可选择解析占位符。例如,这对于提供与环境相关的日志配置非常有用。
  • 将配置服务器嵌入到应用中,让它自己从 Git 仓库中进行配置,而不是作为服务客户端的独立应用程序运行。因此,必须设置一些属性和/或移除 @EnableConfigServer 注解,这取决于使用情况。
  • 在 Spring Netflix Eureka 服务发现中提供配置服务器,并在配置客户端中启用自动服务器发现。如果服务器没有固定位置或位置移动,这一点就变得很重要。

Ref:https://www.baeldung.com/spring-cloud-configuration