Spring Cloud Config Server为外部配置提供基于HTTP资源的API(名称—值对或等效的YAML内容),通过使用 @EnableConfigServer
注解,服务器可嵌入Spring Boot应用程序中,因此,以下应用程序是配置服务器:
@SpringBootApplication @EnableConfigServer public class ConfigServer { public static void main(String[] args) { SpringApplication.run(ConfigServer.class, args); } }
与所有Spring Boot应用程序一样,它默认在端口8080上运行,但你可以通过各种方式将其切换到更传统的端口8888。最简单的,也是设置默认配置存储库,是通过 spring.config.name=configserver
启动它(Config Server jar中有一个 configserver.yml
),另一种方法是使用你自己的 application.properties
,如以下示例所示:
server.port: 8888 spring.cloud.config.server.git.uri: file://${user.home}/config-repo
其中 ${user.home}/config-repo
是一个包含YAML和属性文件的git存储库。
在Windows上,如果文件URL是绝对的驱动器前缀,则需要额外的“/”(例如, file:///${user.home}/config-repo
)。
以下清单显示了在前面的示例中创建git存储库的步骤:
$ cd $HOME $ mkdir config-repo $ cd config-repo $ git init . $ echo info.foo: bar > application.properties $ git add -A . $ git commit -m "Add application.properties"
使用git存储库的本地文件系统仅用于测试,生产中你应该使用服务器托管配置存储库。
如果只保留文本文件,则配置存储库的初始克隆可以快速有效,如果存储二进制文件(尤其是大型文件),则第一次请求配置可能会出现延迟或服务器中遇到内存不足错误。
应该在哪里存储配置服务器的配置数据?管理此行为的策略是 EnvironmentRepository
,为 Environment
对象提供服务,此 Environment
是Spring Environment
中域的浅拷贝(包括 propertySources
作为主要功能), Environment
资源由三个变量参数化:
{application}
,它映射到客户端的 spring.application.name
。 {profile}
,它映射到客户端(逗号分隔列表)的 spring.profiles.active
。 {label}
,这是标记配置文件集“版本化”的服务器端特性。
存储库实现通常表现得像Spring Boot应用程序,从 spring.config.name
等于 {application}
参数, spring.profiles.active
等于 {profiles}
参数加载配置文件。配置文件的优先规则也与常规Spring Boot应用程序中的规则相同:活动配置文件优先于默认配置文件,如果有多个配置文件,则最后一个配置文件获胜(类似于向 Map
添加条目)。
以下示例客户端应用程序具有此bootstrap配置:
spring: application: name: foo profiles: active: dev,mysql
像通常一样,Spring Boot应用程序也可以通过环境变量或命令行参数来设置这些属性。
如果存储库是基于文件的,则服务器从 application.yml
(在所有客户端之间共享)和 foo.yml
(以 foo.yml
优先)创建 Environment
。如果YAML文件中包含指向Spring配置文件的文档,那么这些文档将以更高的优先级应用(按列出的配置文件的顺序)。如果存在特定配置文件的YAML(或属性)文件,则这些文件的优先级也高于默认值,较高的优先级转换为 Environment
中先前列出的 PropertySource
(这些相同的规则适用于独立的Spring Boot应用程序)。
你可以将 spring.cloud.config.server.accept-empty
设置为 false
,以便如果应用程序找不到,则Server返回HTTP 404状态,默认情况下,此标志设置为 true
。
Config Server附带一个健康指示器,用于检查配置的 EnvironmentRepository
是否正常工作,默认情况下,它会向 EnvironmentRepository
请求名为 app
的应用程序、 default
配置文件以及 EnvironmentRepository
实现提供的默认标签。
你可以配置健康指示器以检查更多应用程序以及自定义配置文件和自定义标签,如以下示例所示:
spring: cloud: config: server: health: repositories: myservice: label: mylabel myservice-dev: name: myservice profiles: development
你可以通过设置 spring.cloud.config.server.health.enabled=false
来禁用监控指示器。
你可以以对你有意义的任何方式保护你的Config Server(从物理网络安全到OAuth2承载令牌),因为Spring Security和Spring Boot为许多安全安排提供支持。
要使用默认的Spring Boot配置的HTTP Basic安全性,请在类路径中包含Spring Security(例如,通过 spring-boot-starter-security
),默认值为 user
的用户名和随机生成的密码,随机密码在实践中没有用,因此建议你配置密码(通过设置 spring.security.user.password
)并对其进行加密(有关如何执行此操作的说明,请参阅下文)。
要使用加密和解密特性,你需要在JVM中安装完整的JCE(默认情况下不包括),你可以从Oracle下载“Java Cryptography Extension(JCE)Unlimited Strength Jurisdiction Policy Files”并按照安装说明进行操作(实际上,你需要将JRE lib/security
目录中的两个策略文件替换为你下载的策略文件)。
如果远程属性源包含加密内容(以 {cipher}
开头的值),则在通过HTTP发送到客户端之前对它们进行解密,此设置的主要优点是,属性值在“静止”时不必是纯文本格式(例如,在git存储库中)。如果某个值无法解密,则会从属性源中删除该值,并添加一个附加属性,该属性具有相同的键但前缀为 invalid
,且值为“不适用”(通常为 <n/a>
),这主要是为了防止密文被用作密码并意外泄露。
如果为配置客户端应用程序设置远程配置存储库,则它可能包含类似于以下内容的 application.yml
:
spring: datasource: username: dbuser password: '{cipher}FKSAJDFGYOS8F7GLHAKERGFHLSAJ'
.properties
文件中的加密值不能用引号括起来,否则,该值不会被解密,以下示例显示了有效的值:
spring.datasource.username: dbuser spring.datasource.password: {cipher}FKSAJDFGYOS8F7GLHAKERGFHLSAJ
你可以安全地将此纯文本推送到共享的git存储库,并且密码仍然受到保护。
服务器还公开 /encrypt
和 /decrypt
端点(假设这些端点是安全的并且只能由授权代理访问),如果编辑远程配置文件,则可以使用Config Server通过POST到 /encrypt
端点来加密值,如以下示例所示:
$ curl localhost:8888/encrypt -d mysecret 682bc583f4641835fa2db009355293665d2647dade3375c0ee201de2a49f7bda
如果你加密的值中包含需要进行URL编码的字符,则应使用 --data-urlencode
选项进行 curl
以确保它们已正确编码。
请确保不要在加密值中包含任何 curl
命令统计信息,将值输出到文件可以帮助避免此问题。
通过 /decrypt
也可以使用反向操作(前提是服务器配置了对称密钥或完整密钥对),如以下示例所示:
$ curl localhost:8888/decrypt -d 682bc583f4641835fa2db009355293665d2647dade3375c0ee201de2a49f7bda mysecret
如果你用 curl
测试,那么使用 --data-urlencode
(替代 -d
)或设置一个显式的 Content-Type: text/plain
来确保 curl
在有特殊字符时正确编码数据('+'特别棘手)。
获取加密值并添加 {cipher}
前缀,然后再将其放入YAML或属性文件中,然后再提交并将其推送到远程(可能不安全)存储。
/encrypt
和 /decrypt
端点也接受 /*/{name}/{profiles}
形式的路径,当客户端调用主环境资源时,可用于在每个应用程序(名称)和每个配置文件的基础上控制加密。
要以这种精细的方式控制加密,你还必须提供类型为 TextEncryptorLocator
的 @Bean
,它为每个名称和配置文件创建不同的加密器,默认情况下提供的那个不会这样做(所有加密都使用相同的密钥)。
spring命令行客户端(安装了Spring Cloud CLI扩展)也可用于加密和解密,如以下示例所示:
$ spring encrypt mysecret --key foo 682bc583f4641835fa2db009355293665d2647dade3375c0ee201de2a49f7bda $ spring decrypt --key foo 682bc583f4641835fa2db009355293665d2647dade3375c0ee201de2a49f7bda mysecret
要使用文件中的密钥(例如用于加密的RSA公钥),请在密钥值前加上“@”并提供文件路径,如以下示例所示:
$ spring encrypt mysecret --key @${HOME}/.ssh/id_rsa.pub AQAjPgt3eFZQXwt8tsHAVv/QHiY5sI2dRcR+...
--key
参数是必需的(尽管有 --
前缀)。
Config Server可以使用对称(共享)密钥或非对称密钥(RSA密钥对),非对称选择在安全性方面更优越,但使用对称密钥通常更方便,因为它是在 bootstrap.properties
中配置的单个属性值。
要配置对称密钥,需要将 encrypt.key
设置为秘密字符串(或使用 ENCRYPT_KEY
环境变量将其排除在纯文本配置文件之外)。
无法使用 encrypt.key
配置非对称密钥。
要配置非对称密钥,请使用密钥库(例如,由JDK附带的 keytool
实用工具创建),密钥库属性是 encrypt.keyStore.*
, *
等于:
属性 | 描述 |
---|---|
encrypt.keyStore.location
|
包含 Resource
的位置 |
encrypt.keyStore.password
|
保存解锁密钥库的密码 |
encrypt.keyStore.alias
|
标识要使用存储中的哪个密钥 |
加密是使用公钥完成的,并且需要私钥进行解密,因此,原则上,如果只想加密(并准备使用私钥本地解密值),则只配置服务器中的公钥。实际上,你可能不希望在本地进行解密,因为它会围绕所有客户端传播密钥管理过程,而不是将其集中在服务器中,另一方面,如果你的配置服务器相对不安全且只有少数客户端需要加密属性,那么它可能是一个有用的选项。
要创建用于测试的密钥库,可以使用类似于以下内容的命令:
$ keytool -genkeypair -alias mytestkey -keyalg RSA / -dname "CN=Web Server,OU=Unit,O=Organization,L=City,S=State,C=US" / -keypass changeme -keystore server.jks -storepass letmein
将 server.jks
文件放在类路径中(例如),然后在 bootstrap.yml
中为Config Server创建以下设置:
encrypt: keyStore: location: classpath:/server.jks password: letmein alias: mytestkey secret: changeme
除了加密属性值中的 {cipher}
前缀之外,Config Server还会在(Base64编码)密文开头之前查找零个或多个 {name:value}
前缀,密钥传递给 TextEncryptorLocator
,它可以执行为密文定位 TextEncryptor
所需的任何逻辑。如果已配置密钥库( encrypt.keystore.location
),则默认定位器将查找具有 key
前缀提供的别名的密钥,密文类似于以下内容:
foo: bar: `{cipher}{key:testkey}...`
定位器查找名为“testkey”的密钥,也可以通过在前缀中使用 {secret:…}
值来提供秘密,但是,如果未提供,则默认使用密钥库密码(这是你在构建密钥库时未指定秘密),如果你提供秘密,你还应该使用自定义 SecretLocator
加密秘密。
当密钥仅用于加密几个字节的配置数据时(也就是说,它们没有在其他地方使用),在加密方面几乎不需要密钥轮换。但是,你可能偶尔需要更改密钥(例如,在发生安全漏洞时),在这种情况下,所有客户端都需要更改其源配置文件(例如,在git中)并在所有密文中使用新的 {key:…}
前缀,请注意,客户端需要首先检查Config Server密钥库中的密钥别名是否可用。
如果你想让Config Server处理所有加密和解密, {name:value}
前缀也可以作为纯文本添加发布到 /encrypt
端点。
有时你希望客户端在本地解密配置,而不是在服务器中执行此操作。在这种情况下,如果你提供 encrypt.*
配置来定位密钥,你仍然可以拥有 /encrypt
和 /decrypt
端点,但是你需要通过在 bootstrap.[yml|properties]
中放置 spring.cloud.config.server.encrypt.enabled=false
来明确地关闭输出属性的解密,如果你不关心端点,那么如果你不配置密钥或启用标志,它应该可以工作。