在应用运行时经常会有修改配置的需求,那么在Spring Cloud Config中如何让修改Git仓库的配置动态生效呢?我们在ConfigClientApplication类上加上@RefreshScope注解并在Config Client的pom.xml中添加spring-boot-starter-actuator监控模块,其中包含了/refresh刷新API,并启动Config Client。如下为pom文件中的依赖项:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
在配置完之后,我们进行如下尝试来验证配置时进行了热生效。
步骤1 访问 http://localhost:8084/say 得到响应“key1:master-test-value1”。
步骤2 将configServerDemo-test.properties的内容修改为master-test-value2并通过Git提交对配置文件的修改。
步骤3 请求Config Client的 http://localhost:8084/say ,依旧是“key1:master-test-value1”。因为Client未接到任何通知进行本地配置更新。
步骤4 通过POST访问 http://localhost:8084/refresh 得到响应["key1"],表明key1已被更新。
步骤5 访问 http://localhost:8084/say ,相应内容已经变成了master-test-value2。
然而,如果每次修改了配置文件就要手动请求/refresh,这一定不是我们想要的效果。在第11章中我们将介绍如何使用Bus来通知Spring Cloud Config,如图3-2所示。
图3-2 Spring Cloud Bus与
在上文中我们以在配置文件中指定配置中心Config Server的实例地址的方式来定位Config Server。一旦遇到Config Server宕机,Config Client将无法继续获取配置,且对Config Server进行横向扩展时也需要修改每一个Config Client的配置文件。当单台Config Server压力过大时,客户端也无法做到负载均衡。
Spring Cloud针对Config Server同样支持通过Eureka进行服务注册的方式。我们将Config Server的所有实例以服务提供方的形式注册在Eureka上。Config Client以服务消费方的形式去Eureka获取Config Server的实例,这样也就同时支持由Eureka组件提供的故障转移、服务发现、负载均衡等功能。
我们接下来对之前的Config Server进行改造,在Config Server的Maven pom.xml上增加Eureka的依赖:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency>
在配置文件application.yml中追加Eureka的注册中心地址的配置:
eureka: client: serviceUrl: defaultZone: http://localhost:8989/eureka/
在ConfigServerApplication.java主类中标注@EnableEurekaClient注解。
接下来,启动Config Server,并查看Eureka控制台,可以看见已经注册的服务提供方。之后需要让Config Client去Eureka获取Config Server地址。我们来对Config Server进行改造。
在Maven中新增对Eureka的依赖:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency>
在bootstrap.yml中新增Eureka注册中心地址的配置,并去掉spring.cloud.config.uri的静态指定。通过spring.cloud.config.discovery.enabled指定打开Spring Cloud Config的动态发现服务功能的开关,通过spring.cloud.config.discovery.serviceId指定在注册中心的配置中心中的ServiceId。
eureka: client: serviceUrl: defaultZone: http://localhost:8989/eureka/ spring: application: name: cloudConfigDemo${server.port} cloud: config: profile: dev label: master name: configServerDemo discovery: enabled: true service-id: myConfigServer #uri: http://localhost:8888/
在ConfigClientApplication.java主类中增加@EnableDiscoveryClient注解,使其打开服务发现客户端功能。
启动Config Client时可以发现,如果配置服务部署多份,通过负载均衡,可以实现高可用。
使用Spring Cloud Config时,可能有些场景需要将配置服务暴露在公网或者其他需要加权限安全控制的场景。可以使用Spring Security来整合Spring Cloud Config。
使用Spring Boot默认的基于HTTP安全方式,仅仅需要引入Spring Security依赖(如:可以通过spring-boot-starter-security)。引入此依赖的默认情况是使用一个用户名和一个随机产生的密码,这种方式并不是很靠谱,因此,建议通过spring-boot-starter-security配置密码,并对其进行加密处理。
在默认情况下,启动Config Server时,会看到启动日志中有如下类似信息:
b.a.s.AuthenticationManagerConfiguration : Using default security password: 7bbc28c2-b60f-4996-8eb5-87b4f57e976c
这就是Spring Security默认生成的密码,同样也可以通过配置文件自定义账号和密码:
security: user: name: testuser password: testpassword
在实际生产环境使用过程中,就算加入了账号和密码等方式的权限控制,数据存储在Config Server中依旧可能被泄露,那么可以对数据加密后再存储在Config Server中。
如果远程资源是一个经过加密的内容(以{cipher}开头),在发送给客户端之前运行时会被解密。这样,配置内容就不用明文存放了。
我们先来使用JDK自带的keytool工具生成加解密时所需要用到的密钥:
keytool -genkey -alias cloudtest (别名) -keypass 123456 (别名密码) -keyalg RSA (算法) -validity 365 (有效期,天) -keystore mykey.keystore (指定生成证书的位置和证书名称) -storepass mypass (获取keystore信息的密码)
接下来需要填入一些无关紧要的信息:
你的名字与姓氏是什么? [Unknown]: hjh 你的组织单位名称是什么? [Unknown]: spring 你的组织名称是什么? [Unknown]: spring 你所在的城市或区域名称是什么? [Unknown]: shanghai 你所在的省/市/自治区名称是什么? [Unknown]: cn 该单位的双字母国家/地区代码是什么? [Unknown]: cn CN=hjh, OU=spring, O=spring, L=shanghai, ST=cn, C=cn是否正确? [否]: y
接下来将密钥信息复制进Resources目录并配置进Config Server配置文件中:
encrypt: key-store: location: classpath:mykey.keystore password: mypass alias: cloudtest secret: 123456application.yml
在location中也可以使用file://来配置文件路径。
接下来尝试访问 http://localhost:8888/encrypt 并以POST方式提交需要加密的内容。
$ curl localhost:8888/encrypt -d mysecret AQATZVzrgr9M+doCEiRdL44JD2rB+A2HzX/I6Sec6w04+VW+znApTHZoiJhL0Fn4+3u73aUi5euejvokwmAx+ttBPX8UrhxMcDHZmqj1ADm2XAqX1/NEJtkcfVSFCrkyAztzlT/u+6/uzHRUMZhiJDn41yYtGKtt9/zlni9WKcEBxhSb2XMYuJL21EL2q4w2rD9awLYfJBy4MD6fbPC2mlZ0XCFuCDR7mslneLQtB/bkKcVUR/p5g8GJ8qWUt9T6DGQ52QgxTCoRvJcUFzulRD+A3b4UhuHmumdP0i7wM+hnTI+6h/HXVZ33Ju8SGRtnYXp7Bnz69T4NPZRT7Ov6S/4/IJMObwrSNSfZv7tAV2BSRj4U6xhBCCAcXdVrTHQzlpM=
请求返回的内容就是服务端根据我们配置的密钥加密后的结果。
同样,以POST方式请求 http://localhost:8888/decrypt 并传入密文,将会返回解密后的结果。
$ curl localhost:8888/decrypt -d AQATZVzrgr9M+doCEiRdL44JD2rB+A2HzX/I6Sec6w04+VW+znApTHZoiJhL0Fn4+3u73aUi5euejvokwmAx+ttBPX8UrhxMcDHZmqj1ADm2XAqX1/NEJtkcfVSFCrkyAztzlT/u+6/uzHRUMZhiJDn41yYtGKtt9/zlni9WKcEBxhSb2XMYuJL21EL2q4w2rD9awLYfJBy4MD6fbPC2mlZ0XCFuCDR7mslneLQtB/bkKcVUR/p5g8GJ8qWUt9T6DGQ52QgxTCoRvJcUFzulRD+A3b4UhuHmumdP0i7wM+hnTI+6h/HXVZ33Ju8SGRtnYXp7Bnz69T4NPZRT7Ov6S/4/IJMObwrSNSfZv7tAV2BSRj4U6xhBCCAcXdVrTHQzlpM= asdasd
注意 如果在请求/encrypt和/decrypt的时候服务端抛出“Illegal key size”异常,则表明JDK中没有安装Java Cryptography Extension。
Java Cryptography Extension(JCE)是一组包,提供用于加密、密钥生成和协商以及MAC(Message Authentication Code)算法的框架和实现,提供对对称、不对称、块和流密码的加密支持,还支持安全流和密封的对象。它不提供对外出口,用它开发并完成封装后将无法调用。
下载地址为 http://www.oracle.com/technetwork/java/javase/downloads/index.html ,下载并解压完成后,将其复制到JDK/jre/lib/security中即可。
在实际使用过程中,只需要将生成好的密文以{cipher}开头填入配置中即可。
spring: datasource: username: dbuser password: '{cipher}FKSAJDFGYOS8F7GLHAKERGFHLSAJ'
如果使用properties格式配置文件,则加密数据不要加上双引号。可以在application.properties中加入如下配置:
spring.datasource.username: dbuser spring.datasource.password: {cipher}FKSAJDFGYOS8F7GLHAKERGFHLSAJ
这样就可以安全共享此文件,同时可以保护密码。
有的时候需要客户端自行对密文进行解密,而不是在服务端解密。这就需要明确指定配置数据在服务端发出时不解密:spring.cloud.config.server.encrypt.enabled=false。
在某些场景可能不总是以YAML、Properties、JSON等格式获取配置文件,可能内容是自定义格式的,希望Config Server将其以纯文本方式来处理而不做其他加工。Config Server提供了一个访问端点/{name}/{profile}/{label}/{path}来支持这种需求,这里的{path}是指文件名。
当资源文件被找到后,与常规的配置文件一样,也会先处理占位符。例如,在上文案例的Git仓库中上传:
nginx.conf server { listen 80; mykey ${key1}; }
接下来重启Config Server并请求Nginx的配置文件。如尝试请求 http://localhost:8888/configServerDemo/dev/master/nginx.conf ,将会得到如下响应:
server { listen 80; mykey master-dev-value-dev; }
可以看到正常匹配了我们上传的自定义格式文件,并替换了占位符。