1. 概述

Spring Cloud Config 是 Spring 提供的客户端/服务器架构方案,用于在多个应用和环境之间存储和分发分布式配置。

理想情况下,这个配置存储应该通过 Git 版本控制进行管理,并且可以在应用运行时修改。虽然它与 Spring 应用完美契合(支持所有配置文件格式以及 EnvironmentPropertySource@Value 等特性),但它也能在任何运行任何编程语言的环境中使用。

本文将重点介绍如何:

  • 设置基于 Git 的配置服务器
  • 在简单的 REST 应用服务器中使用它
  • 构建包含加密属性值的安全环境

2. 项目设置与依赖

首先创建两个新的 Maven 项目。服务器项目需要以下依赖:

<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>

客户端项目则只需要:

<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 或本地文件系统协议。

⚠️ 踩坑提醒:如果计划使用多个指向同一仓库的配置服务器实例,建议配置服务器将仓库克隆到本地临时文件夹。但要注意:启用双因素认证的私有仓库很难处理!遇到这种情况,直接克隆到本地文件系统再操作会更省心。

我们还需要在 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 仓库,创建属性文件并填充值。

配置文件命名规则类似 Spring 的 application.properties,但用客户端的应用名(如 spring.application.name 的值)替换 "application",后跟短横线和激活的 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}*:客户端激活的 profile

例如,获取 master 分支下开发环境配置:

$> curl http://root:s3cr3t@localhost:8888/config-client/development/master

6. 客户端实现

接下来实现客户端。这是一个简单的客户端应用,包含一个带 GET 方法的 REST 控制器。

关键配置:Spring Boot 2.4 引入了通过 spring.config.import 加载配置的新方式,这是连接 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);
    }
}

application.properties 中配置应用名、激活 profile 和连接信息:

spring.application.name=config-client
spring.profiles.active=development
spring.config.import=optional:configserver:http://root:s3cr3t@localhost:8888

这会连接到 http://localhost:8888 的 Config Server,并使用 HTTP Basic 认证。也可通过 spring.cloud.config.usernamespring.cloud.config.password 单独设置。

⚠️ 注意:如果希望服务在无法连接 Config Server 时启动失败,移除 optional: 前缀即可。

测试配置是否正确加载:

$> curl http://localhost:8080/whoami/Mr_Pink

若返回以下内容,说明配置服务器和客户端工作正常:

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} 开头,可通过调用 /encrypt 接口生成(需配置对称密钥或密钥对)。

解密接口也通过 /decrypt 提供。两个接口都支持包含应用名和 profile 占位符的路径:*/*/{name}/{profile}*,这对按客户端控制加密很有用。但使用前需先配置加密密钥。

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 密钥管理

配置服务器默认支持对称和非对称加密:

  • 对称加密:在 application.properties 中设置 encrypt.key 为自定义密钥,或通过环境变量 ENCRYPT_KEY 传递。
  • 非对称加密:设置 encrypt.keyPEM 编码字符串,或配置 keystore

为构建高安全环境,我们选择 keystore 方案。先用 Java keytool 生成包含 RSA 密钥对的 keystore:

$> 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 添加到服务器配置并重启:

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 使用多密钥

若需为不同应用使用不同密钥,可在 {cipher} 前缀和 BASE64 编码值之间添加 {name:value} 形式的前缀。

服务器原生支持 {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 时,需实现 TextEncryptorLocator 类型的 @Bean 来处理密钥查找。

7.4 提供加密属性

若要禁用服务端加密,在本地处理属性解密,可在服务器配置中添加:

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

同时删除所有 encrypt. 属性以禁用 REST 接口。

8. 总结

现在我们已经能够创建配置服务器,从 Git 仓库向客户端应用提供配置文件集。还可以实现更多功能:

  • 提供 YAMLProperties 格式配置(而非 JSON),并解析占位符。这在非 Spring 环境中尤其有用。
  • 提供纯文本配置文件(可选解析占位符),例如提供环境相关的日志配置。
  • 将配置服务器嵌入应用中,使其从 Git 仓库自我配置,而非作为独立服务运行。此时需根据场景调整属性或移除 @EnableConfigServer
  • 将配置服务器注册到 Spring Netflix Eureka 服务发现,并在客户端启用自动服务器发现。当服务器位置不固定或可能变动时这很重要。

本文源码可在 Github 获取。


原始标题:Quick Intro to Spring Cloud Configuration

» 下一篇: Cucumber Spring集成