Quick Intro to Spring Cloud Configuration – Spring云配置的快速介绍

最后修改: 2016年 8月 5日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

Spring Cloud Config is Spring’s client/server approach for storing and serving distributed configurations across multiple applications and environments.

Spring Cloud Config是Spring的客户/服务器方法,用于存储和服务跨多个应用程序和环境的分布式配置。

This configuration store is ideally versioned under Git version control and can be modified at application runtime. While it fits very well in Spring applications using all the supported configuration file formats together with constructs like Environment, PropertySource, or @Value, it can be used in any environment running any programming language.

这个配置存储最好是在Git版本控制下的版本,并且可以在应用运行时进行修改。虽然它非常适用于Spring应用程序,使用所有支持的配置文件格式以及EnvironmentPropertySource或@Value等结构,但它可以用于运行任何编程语言的任何环境。

In this tutorial, we’ll focus on how to set up a Git-backed config server, use it in a simple REST application server, and set up a secure environment including encrypted property values.

在本教程中,我们将重点介绍如何设置一个Git支持的配置服务器,在一个简单的REST应用服务器中使用它,并设置一个包括加密属性值的安全环境。

2. Project Setup and Dependencies

2.项目设置和依赖性

First, we’ll create two new Maven projects. The server project is relying on the spring-cloud-config-server module, as well as the spring-boot-starter-security and spring-boot-starter-web starter bundles:

首先,我们要创建两个新的Maven项目。服务器项目依赖于spring-cloud-config-server模块,以及spring-boot-starter-securityspring-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>

However, for the client project, we only need the spring-cloud-starter-config and the spring-boot-starter-web modules:

然而,对于客户项目,我们只需要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. A Config Server Implementation

3.一个配置服务器的实现

The main part of the application is a config class, more specifically a @SpringBootApplication, which pulls in all the required setup through the auto-configure annotation @EnableConfigServer:

应用程序的主要部分是一个配置类,更确切地说,是一个@SpringBootApplication,它通过自动配置注解@EnableConfigServer:拉入所有需要的设置。

@SpringBootApplication
@EnableConfigServer
public class ConfigServer {
    
    public static void main(String[] arguments) {
        SpringApplication.run(ConfigServer.class, arguments);
    }
}

Now we need to configure the server port on which our server is listening and a Git-url, which provides our version-controlled configuration content. The latter can be used with protocols like http, ssh, or a simple file on a local filesystem.

现在我们需要配置我们的服务器所监听的服务器port和一个Git-url,它提供我们的版本控制的配置内容。后者可以使用httpssh或本地文件系统上的简单file等协议。

Tip: If we’re planning to use multiple config server instances pointing to the same config repository, we can configure the server to clone our repo into a local temporary folder. But be aware of private repositories with two-factor authentication; they’re difficult to handle! In such a case, it’s easier to clone them on our local filesystem and work with the copy.

提示:如果我们计划使用多个配置服务器实例指向同一个配置仓库,我们可以配置服务器将我们的 repo 克隆到一个本地临时文件夹。但要注意有双因素认证的私有资源库,它们很难处理在这种情况下,在我们的本地文件系统上克隆它们并使用副本会更容易。

There are also some placeholder variables and search patterns for configuring the repository-url available; however, this is beyond the scope of our article.  If you’re interested in learning more, the official documentation is a good place to start.

还有一些用于配置repository-urlplaceholder变量和搜索模式;但是,这已经超出了我们文章的范围。 如果你有兴趣了解更多,官方文档是一个好的开始。

We also need to set a username and password for the Basic-Authentication in our application.properties to avoid an auto-generated password on every application restart:

我们还需要在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. A Git Repository as Configuration Storage

4.作为配置存储的Git存储库

To complete our server, we have to initialize a Git repository under the configured url, create some new properties files, and populate them with some values.

为了完成我们的服务器,我们必须在配置的URL下初始化一个Git仓库,创建一些新的属性文件,并在其中填充一些值。

The name of the configuration file is composed like a normal Spring application.properties, but instead of the word ‘application,’ a configured name, such as the value of the property ‘spring.application.name’, of the client is used, followed by a dash and the active profile. For example:

配置文件的名称像普通的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'

Troubleshooting: If we run into ssh-related authentication issues, we can double check ~/.ssh/known_hosts and ~/.ssh/authorized_keys on our ssh server.

故障排除:如果我们遇到ssh相关的认证问题,我们可以在ssh服务器上仔细检查~/.ssh/known_hosts~/.ssh/authorized_keys

5. Querying the Configuration

5.查询配置

Now we’re able to start our server. The Git-backed configuration API provided by our server can be queried using the following paths:

现在我们能够启动我们的服务器了。我们的服务器所提供的Git支持的配置API可以通过以下路径进行查询。

/{application}/{profile}[/{label}]
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties

The {label} placeholder refers to a Git branch, {application} to the client’s application name, and the {profile} to the client’s current active application profile.

{label}占位符指的是 Git 分支,{application}指的是客户端的应用程序名称,{profile}指的是客户端当前的活动应用程序配置文件。

So we can retrieve the configuration for our planned config client running under the development profile in branch master via:

因此,我们可以通过以下方式为我们计划中的配置客户端检索在分支master的开发配置文件下运行的配置。

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

6. The Client Implementation

6.客户的实施

Next, let’s take care of the client. This will be a very simple client application, consisting of a REST controller with one GET method.

接下来,让我们来处理一下客户端的问题。这将是一个非常简单的客户端应用程序,由一个REST控制器和一个GET方法组成。

To fetch our server, the configuration must be placed in the application.properties file. Spring Boot 2.4 introduced a new way to load configuration data using the spring.config.import property, which is now the default way to bind to Config Server:

为了获取我们的服务器,必须将配置放在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);
    }
}

In addition to the application name, we also put the active profile and the connection-details in our application.properties:

除了应用程序名称,我们还在application.properties中放入活动配置文件和连接细节。

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

This will connect to the Config Server at http://localhost:8888 and will also use HTTP basic security while initiating the connection. We can also set the username and password separately using spring.cloud.config.username and spring.cloud.config.password properties, respectively.

这将连接到配置服务器http://localhost:8888,在启动连接时也将使用HTTP基本安全。我们还可以分别使用spring.cloud.config.usernamespring.cloud.config.password属性来设置用户名和密码。

In some cases, we may want to fail the startup of a service if it isn’t able to connect to the Config Server. If this is the desired behavior, we can remove the optional: prefix to make the client halt with an exception.

在某些情况下,如果一个服务无法连接到配置服务器,我们可能想让它的启动失败。如果这是我们想要的行为,我们可以删除optional:前缀,以使客户端以异常方式停止运行。

To test if the configuration is properly received from our server, and the role value gets injected in our controller method, we simply curl it after booting the client:

为了测试配置是否正确地从我们的服务器接收,并且角色值被注入到我们的控制器方法中,我们在启动客户端后简单地卷曲了它。

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

If the response is as follows, our Spring Cloud Config Server and its client are working fine for now:

如果响应如下,我们的Spring Cloud Config Server和其客户端目前工作正常。

Hello! You're Mr_Pink and you'll become a(n) Developer...

7. Encryption and Decryption

7.加密和解密

Requirement: To use cryptographically strong keys together with Spring encryption and decryption features, we need the ‘Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files’ installed in our JVM. These can be downloaded, for example, from Oracle. To install, follow the instructions included in the download. Some Linux distributions also provide an install-able package through their package managers.

要求:为了在Spring加密和解密功能中使用加密强度高的密钥,我们需要在JVM中安装‘Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files’例如,可以从Oracle下载这些文件。要进行安装,请遵循下载中的说明。一些Linux发行版也通过其软件包管理器提供了一个可安装的软件包。

Since the config server is supporting the encryption and decryption of property values, we can use public repositories as storage for sensitive data, like usernames and passwords. Encrypted values are prefixed with the string {cipher}, and can be generated by a REST-call to the path ‘/encrypt’ if the server is configured to use a symmetric key or a key pair.

由于配置服务器支持属性值的加密和解密,我们可以使用公共资源库作为敏感数据的存储,如用户名和密码。加密值的前缀是字符串{cipher},,如果服务器被配置为使用对称密钥或密钥对,可以通过REST-调用路径‘/encrypt’来生成。

An endpoint to decrypt is also available. Both endpoints accept a path containing placeholders for the name of the application and its current profile: ‘/*/{name}/{profile}’. This is especially useful for controlling cryptography per client. However, before they can be useful, we have to configure a cryptographic key, which we’ll do in the next section.

一个解密的端点也是可用的。这两个端点都接受一个包含应用程序名称和当前配置文件占位符的路径。‘/*/{name}/{profile}’。这对于控制每个客户端的加密技术特别有用。然而,在它们发挥作用之前,我们必须配置一个加密密钥,我们将在下一节进行。

Tip: If we use curl to call the en-/decryption API, it’s better to use the –data-urlencode option (instead of –data/-d), or set the ‘Content-Type’ header explicit to ‘text/plain’. This ensures a correct handling of special characters like ‘+’ in the encrypted values.

提示:如果我们使用curl调用en-/decryption API,最好使用 -data-urlencode 选项(而不是-data/-d),或者将 “Content-Type “头明确设置为‘text/plain’。这可以确保正确处理加密值中的特殊字符,如’+’。

If a value can’t be decrypted automatically while fetching through the client, its key is renamed with the name itself, prefixed by the word ‘invalid.’ This should prevent the use of an encrypted value as password.

如果一个值在通过客户端获取时不能自动解密,它的key会被重新命名为名称本身,前面加上’invalid’这个词。这应该可以防止将加密的值作为密码使用。

Tip: When setting-up a repository containing YAML files, we have to surround our encrypted and prefixed values with single-quotes. However, this isn’t the case with Properties.

提示:当设置包含YAML文件的版本库时,我们必须用单引号包围我们的加密和前缀的值。然而,属性就不是这样了。

7.1. CSRF

7.1 CSRF

By default, Spring Security enables CSRF protection for all the requests sent to our application.

默认情况下,Spring Security 对发送到我们应用程序的所有请求启用CSRF保护。

Therefore, to be able to use the /encrypt and /decrypt endpoints, let’s disable the CSRF for them:

因此,为了能够使用/encrypt/decrypt端点,让我们为它们禁用CSRF。

@Configuration
public class SecurityConfiguration {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.csrf()
          .ignoringAntMatchers("/encrypt/**")
          .ignoringAntMatchers("/decrypt/**");

        //...
    }
}

7.2. Key Management

7.2.密钥管理

By default, the config server is able to encrypt property values in a symmetric or asymmetric way.

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

To use symmetric cryptography, we simply have to set the property ‘encrypt.key’ in our application.properties to a secret of our choice. Alternatively, we can pass-in the environment variable ENCRYPT_KEY.

要使用对称加密技术,我们只需将application.properties中的‘encrypt.key’属性设置为我们选择的一个秘密或者,我们可以通过环境变量ENCRYPT_KEY

For asymmetric cryptography, we can set ‘encrypt.key’ to a PEM-encoded string value or configure a keystore to use.

对于非对称加密技术,我们可以将‘encrypt.key’设置为PEM编码的字符串值或配置一个keystore来使用。

Since we need a highly secured environment for our demo server, we’ll choose the latter option, along with generating a new keystore, including a RSA key-pair, with the Java keytool first:

由于我们的演示服务器需要一个高度安全的环境,我们将选择后者,同时用Java keytool首先生成一个新的密钥库,包括一个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

Then we’ll add the created keystore to our server’s application.properties and re-run it:

然后,我们将创建的密钥库添加到我们服务器的应用程序.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

Next, we’ll query the encryption-endpoint, and add the response as a value to a configuration in our repository:

接下来,我们将查询加密端点,并将响应作为一个值添加到我们资源库的配置中。

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

To test if our setup works correctly, we’ll modify the ConfigClient class and restart our client:

为了测试我们的设置是否正确,我们将修改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);
    }
}

Finally, a query against our client will show us if our configuration value is being correctly decrypted:

最后,对我们的客户端进行查询将显示我们的配置值是否被正确解密。

$> 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. Using Multiple Keys

7.3.使用多个键

If we want to use multiple keys for encryption and decryption, such as a dedicated one for each served application, we can add another prefix in the form of {name:value} between the {cipher} prefix and the BASE64-encoded property value.

如果我们想使用多个密钥进行加密和解密,比如为每个被服务的应用程序使用一个专用密钥,我们可以在{cipher}前缀和BASE64编码的属性值之间添加另一个{name:value}形式的前缀。

The config server understands prefixes like {secret:my-crypto-secret} or {key:my-key-alias} almost out-of-the-box. The latter option needs a configured keystore in our application.properties. This keystore is searched for a matching key alias. For example:

配置服务器能够理解{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...

For scenarios without keystore, we have to implement a @Bean of type TextEncryptorLocator, which handles the lookup and returns a TextEncryptor-Object for each key.

对于没有钥匙库的场景,我们必须实现一个@Bean类型的TextEncryptorLocator,,它处理查找并为每个钥匙返回一个TextEncryptor-Object。

7.4. Serving Encrypted Properties

7.4.提供加密的属性

If we want to disable server-side cryptography and handle the decryption of property-values locally, we can put the following in our server’s application.properties:

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

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

Furthermore, we can delete all the other ‘encrypt.*’ properties to disable the REST endpoints.

此外,我们可以删除所有其他的’encrypt.*’属性来禁用REST端点。

8. Conclusion

8.结论

Now we’re able to create a configuration server to provide a set of configuration files from a Git repository to client applications. There are also a few other things we can do with such a server.

现在我们能够创建一个配置服务器,从Git资源库向客户端应用程序提供一组配置文件。还有一些其他的事情我们可以用这样的服务器来做。

For example:

比如说。

  • Serve configuration in YAML or Properties format instead of JSON, also with placeholders resolved. This can be useful when using it in non-Spring environments, where the configuration isn’t directly mapped to a PropertySource.
  • Serve plain text configuration files in turn, optionally with resolved placeholders. For instance, this can be useful to provide an environment-dependent logging-configuration.
  • Embed the config server into an application, where it configures itself from a Git repository, instead of running as a standalone application serving clients. Therefore, we must set some properties and/or we must remove the @EnableConfigServer annotation, which depends on the use case.
  • Make the config server available at Spring Netflix Eureka service discovery and enable automatic server discovery in config clients. This becomes important if the server has no fixed location or it moves in its location.

As always, the source code for this article is available over on Github.

一如既往,本文的源代码可在Github上获得。