SpringCloud 笔记6

前记

上一章使用了配置中心服务器和客户端,使得配置可以统一。

当服务实例很多时,都要从配置中心读取文件,这时就可以考虑将配置中心做成一个微服务,将其集群化,从而达到高可用的目的。不需要我们为这些 Config 服务端做任何额外的配置,只需要遵守一个配置规则:将所有的 Config Server 都指向同一个 Git 仓库,这样所有的配置内容就通过统一的共享文件系统来维护,而 Config Client 客户端在指定 Config Server 位置时,只要配置 Config Server 外的均衡负载即可。

架构

高可用 Config Server

添加依赖,使 Config Server 作为服务注册到 Eureka 注册中心:

1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

配置文件 application.properties 中指定注册中心地址:

1
eureka.client.service-url.defaultZone=http://localhost:7000/eureka/

加上 @EnableDiscoveryClient 注解:

1
2
3
4
5
6
7
8
9
@EnableDiscoveryClient
@EnableConfigServer
@SpringBootApplication
public class ConfigServerApplication {

public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}

启动服务后,可以看到已经注册上了:

服务已注册

Config Clien 客户端

同样,客户端也要添加依赖:

1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

添加 @EnableDiscoveryClient 注解:

1
2
3
4
5
6
7
8
@EnableDiscoveryClient
@SpringBootApplication
public class ConfigClientApplication {

public static void main(String[] args) {
SpringApplication.run(ConfigClientApplication.class, args);
}
}

配置文件 bootstrap.properties,不仅要添加注册中心地址,还要启用发现配置服务中心和对应的服务名,而配置服务中心网址则不再需要了,由负载均衡动态分配:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
server.port=30000

spring.application.name=configClient

eureka.client.service-url.defaultZone=http://localhost:7000/eureka/

# 开启通过服务来访问 Config Server
spring.cloud.config.discovery.enabled=true
# 指定 Config Server 注册的服务名
spring.cloud.config.discovery.service-id=configServer

# 配置服务中心网址
# spring.cloud.config.uri=http://localhost:20000/
# 使用的配置
spring.cloud.config.profile=dev
# 配置仓库的分支,默认为 master
spring.cloud.config.label=master

服务已注册

测试:

1
2
curl localhost:30000/from 
I'm from git-dev-1.0

加密解密

官方文档

配置文件会包括大量的敏感信息,比如:数据库的账户与密码等。很显然,如果我们直接将敏感信息以明文的方式存储于微服务应用的配置文件中是非常危险的。针对这个问题,SpringCloud Config 提供了对属性进行加密解密的功能,以保护配置文件中的信息安全。比如:

1
2
spring.datasource.username=root
spring.datasource.password={cipher}dba6505baa81d78bd08799d8d4429de499bd4c2053c05f029e7cfbf143695f5b

在 SpringCloud Config 中通过在属性值前使用 {cipher} 前缀来标注该内容是一个加密值,当微服务客户端来加载配置时,配置中心会自动的为带有 {cipher} 前缀的值进行解密。

使用前提

在使用 SpringCloud Config 的加密解密功能时,有一个必要的前提:配置中心的运行环境中安装的 JCE(Unlimited Strength Java Cryptography Extension) 必须是不限长度的。虽然 JRE 中自带 JCE,但是默认使用的是有长度限制的。可以从 Oracle 官网中下载到:
JDK6:http://www.oracle.com/technetwork/java/javase/downloads/jce-6-download-429243.html
JDK7:http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html

需要将 local_policy.jar 和 US_export_policy.jar 两个文件复制到 $JAVA_HOME/jre/lib/security 目录下,覆盖原有内容。

注意

原文

从 1.8.0_151 版本和 1.8.0_152 版本开始,Java 已经提供了更简单的方法来启用不受限的加密算法强度。

在你的 $JAVA_HOME/jre/lib/security 目录下有个 java.security 文件,比如:

1
2
3
4
5
/jdk1.8.0_152
|- /jre
|- /lib
|- /security
|- java.security

里面有关于使用哪种加密算法强度的配置以及说明(可能被#注释了):

1
crypto.policy=unlimited

开启之后,重启你的 Java 应用即可使用不受限制的加密强度。

$JAVA_HOME/jre/lib/security/policy/ 目录下有 limited 和 unlimited 目录,它们分别存放了相应的 local_policy.jar 和 US_export_policy.jar 文件。

可以通过下面的代码验证是否可以使用弱强度的加密:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public static void securityVerify() throws Exception {
byte[] data = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07};
// create a 64 bit secret key from raw bytes
SecretKey key64 = new SecretKeySpec(
new byte[]{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07},
"Blowfish");

// create a cipher and attempt to encrypt the data block with our key
Cipher c = Cipher.getInstance("Blowfish/ECB/NoPadding");
c.init(Cipher.ENCRYPT_MODE, key64);
c.doFinal(data);
System.out.println("64 bit test: passed");

// create a 192 bit secret key from raw bytes
SecretKey key192 = new SecretKeySpec(
new byte[]{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17},
"Blowfish");

// now try encrypting with the larger key
c.init(Cipher.ENCRYPT_MODE, key192);
c.doFinal(data);

System.out.println("192 bit test: passed");

System.out.println("Tests completed");
}

配置密钥

完成了上面的 JCE 安装后,就可以启动 Config Server 配置中心了。

在控制台中,会输出一些配置中心特有的端点:

  • /encrypt/status:查看加密功能状态的端点
  • /key:查看密钥的端点
  • /encrypt:对请求的body内容进行加密的端点
  • /decrypt:对请求的body内容进行解密的端点

Get 请求 /encrypt/status 查看一下加密功能状态:

1
2
curl http://localhost:20000/encrypt/status
{"description":"No key was installed for encryption service","status":"NO_KEY"}

对称加密

因为没有配置密钥,所以无法使用加密功能。在配置文件 bootstrap.properties 中直接指定密钥信息(对称性密钥):

1
encrypt.key=abc123

特别注意是 bootstrap.properties 配置文件,不是 application.properties,不然无法加载密钥信息。

重启 Config Server,再次访问 /encrypt/status

1
2
curl http://localhost:20000/encrypt/status
{"status":"OK"}

此时,配置中心的加密解密功能就已经可以使用了。

可以尝试下 /encrypt 和 /decrypt 端点来进行加密和解密的功能。(都是POST请求)

测试:

1
2
3
4
5
$ curl http://localhost:20000/encrypt -d abc123
c129bcdd46085f8fdeedfd44cd1ab2918e273cdd6c222a75320764fffa4e0288

$ curl http://localhost:20000/decrypt -d c129bcdd46085f8fdeedfd44cd1ab2918e273cdd6c222a75320764fffa4e0288
abc123

然后给 Config Client 的所有配置文件 configClient-xx.properties 里都加上一个加密过的密码(加密前的密码是 root):

1
password={cipher}d2538dde1147b51a3b1973e06af1abf0efc4bc40bdb6b27cdcf3307524b14bac

然后将配置 push 到 Git 上。

给 Config Client 添加一个控制器:

1
2
3
4
5
6
7
8
9
10
11
12
@RestController
public class TestEncryptController {

// 配置文件中加密的密码
@Value("${password}")
private String password;

@GetMapping("/password")
public String getPassword() {
return String.format("Password: %s", password);
}
}

测试:

1
2
$ curl http://localhost:30000/password         
Password: root

可以看出 Config Server 会自动解密 {cipher} 开头的密文。

有时候我们可能想 Config Server 直接返回密文,而不是解密后的内容,可以在 application.properties 配置中加上:

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

非对称加密

上面使用的是对称加密,还可以使用非对称加密,用 JDK 自带的 keytool 生成 RSA 密钥对(有效期设为365天):

1
2
3
4
5
6
keytool -genkeypair -alias configServer -keyalg RSA \
-dname "CN=myname, OU=company, O=organization, L=city, ST=province, C=china" \
-keystore configServer.jks \
-keypass 123456 \
-storepass 123456 \
-validity 365

将生成的 configServer.jks 复制到 configServer/src/main/resources 目录下,然后在 bootstrap.properties 中配置:

1
2
3
4
5
6
#encrypt.key=abc123

encrypt.key-store.location=classpath:/configServer.jks
encrypt.key-store.alias=configServer
encrypt.key-store.password=123456
encrypt.key-store.secret=123456

测试:

1
2
$ curl http://localhost:20000/encrypt -d abc123
AQCFza1WcPB3CIiAksdogkupcwyfGxWkSB6YybslhDb9REwOl0yDyCu5Mzd7zjCKysscy1o+QH5NKuEqyCtw3ErbTyDBFBlTfpOMqPAwb2yoH2wNlRC/IPz/ztHAVBYVc9i3tPicXTvXPNBjL7iGsYJqBwbWruTr4EejMvCOpdQqYzdjCe/pnnFScUwTvX9wtIrgOZTvBO+1qb4gtVGqwKMptjsMVKCcmUt4GfUD6NfEzdvfIfdohQ0uqyRUZLLCEXUzl2SE5GuIPZvZmivx+Q4Y63dWtGrE9TXwxTOJ2yVQS6fumV+aTt7pC/MU2N5z5+ckJ7KE+KwziDIW/L1OJasL2fvAudYaJadVk1ucsGnqr2BA8VwPwHa9QTD6V8ELRY4=

可以看出非对称加密也设置成功了。

动态更新配置

虽然有了配置中心,但是每次更新了配置后需要重新启动客户端,如果有一堆客户端需要重启呢?可以通过实时更新通知来让它们知道需要更新了,而不是一个个重启它们。这里将使用 SpringCloud Bus,它可以将分布式的节点用轻量的消息代理连接起来。可以用于广播配置文件的更改或者服务之间的通讯,也可以用于监控。这里主要是用它来实现通知微服务架构的配置文件的更改。

总线架构

当 Git 文件更改的时候,管理员通过向其中一个端口为 8882 的 Config Client 发送请求 /bus/refresh/,8882 端口客户端收到请求后会向消息总线发送一个更新消息,并且由总线传递到其他服务,从而使整个微服务集群都达到更新配置文件的目的。

在 Config Client 中添加依赖:

1
2
3
4
5
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
<version>1.3.3.RELEASE</version>
</dependency>

消息总线需要用到 RabbitMQ,Arch 可以参考 ArchWiki,其他系统自行搜索安装。

在配置文件 application.properties 中加上 RabbitMQ 的配置,包括 RabbitMQ 的地址、端口,用户名、密码,代码如下:

1
2
3
4
5
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
# 如果有用户名和密码的话
#spring.rabbitmq.username=
#spring.rabbitmq.password=

在更改了配置并上传后,只需要发送 POST 请求到其中一个 Config Client 客户端,比如:http://localhost:30000/bus/refresh,就可以发现所有其他 Config Client 都会重新读取配置文件。

Author

Zoctan

Posted on

2018-06-17

Updated on

2023-03-14

Licensed under