spring cloud config学习三:安全与加密解密

安全保护

由于配置中心存储的内容比较敏感,做一定的安全处理是必要的。为配置中心实现安全保护的方式有很多,比如物理网络限制, OAuth2 授权等。不过,由于我们的微服务应用和配置中心都是构建与 springboot 基础上的,所以与 spring security 结合使用会更加方便。

加入 spring-boot-starter-security 依赖

 <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-security</artifactId>
</dependency>

默认情况下,我们可以随机获得一个名为user的用户,并且在配置中心启动的时候,在日志中打印出随机密码,具体如下:

Using default security password: 8c374401-d37c-43fc-8f1e-cf6b09b75045

大多数情况下,我们并不会使用随机生成的密码的机制,可以在配置文件中指定用户和密码,比如:

security:
  basic:
    enabled: true
  user:
    name: user
    password: root123456

由于我们已经为config-server设置了安全保护,如果这时候连接到配置中心的客户端没有设置对应的安全信息,在获取配置信息时会返回401错误,所以需要通过配置方法在客户端加入安全校验,

security:
  basic:
    enabled: true
  user:
    name: user
    password: root123456

config server加入spring-boot-starter-security依赖:

<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-security</artifactId>
</dependency>

配置文件(application.yml):

security:
  basic:
    enabled: true
  user:
    name: user
    password: root123456
spring:
  application:
    name: config-server-git
  cloud:
    config:
      server:
          uri: http://git.oschina.net/zhihaomiao/{application}-config
          username: 
          password: 
server:
  port: 9090

启动之后访问url验证:

http://localhost:9090/master/order-service-pro.yml

客户端怎么配置呢?
客户端有二种配置方式,第一种:
通过uri的形式:

spring:
  application:
    name: order-service
  cloud:
    config:
      uri: http://user:root123456@localhost:9090
      profile: pro
      label: master
server:
  port: 6060

第二种方式,通过spring.cloud.config.usernamespring.cloud.config.password二个属性:

spring:
  application:
    name: order-service
  cloud:
    config:
      uri: http://localhost:9090
      username: user
      password: root123456
      profile: pro
      label: master
server:
  port: 6060

The spring.cloud.config.password and spring.cloud.config.username values override anything that is provided in the URI.

spring.cloud.config.password和spring.cloud.config.username的值会重写掉URI的用户名和密码。

Spring Cloud Config Server-Security
Spring Cloud Config Client-Security

在微服务架构中,我们通常会采用DevOps的组织方式来降低因团队间沟通造成的巨大成本,以加速微服务应用的交付能力,这就使得原本运维团队控制的线上信息将交由微服务所属组织的成员自行维护,其中将会包含大量的敏感信息,比如数据库的账户与密码等。显然,明文存储是非常危险的。针对这个问题。 spring cloud config提供了对属性加密解密的功能,以保护配置文件中的信息安全,比如下面的列子:

spring.datasource.username=root
spring.datasource.password={cipher}dsafdsfsdf3r423rewfsdfsdfasdfasdfsadfsadfsadfsa

spring cloud config中通过属性值前使用{cipher}前缀来标注该内容是一个加密值,当微服务客户加载配置时,配置中心会自动为带有{cipher}前缀的值进行解密,通过该机制的实现,运维团队就可以将线上信息的加密资源给微服务团队,而不担心这些敏感信息遭到泄漏了。

在使用spring cloud config的加密解密功能时,为了启动该功能,需要在配置中心环境安装jce(Unlimited Strength Jurisdiction Policy)。虽然,jce时jdk自带的,但是默认使用的是有长度限制的版本。在oracle的官方网站下载它,下载地址:jce1.8版本

下载之后是一个压缩包,里面有三个文件:

local_policy.jar
README.txt
US_export_policy.jar

我们需要将local_policy.jarUS_export_policy.jar两个文件复制到$JAVA_HOME/jre/lib/security目录下。覆盖之前的默认内容。

➜ echo $JAVA_HOME
➜ /Library/Java/JavaVirtualMachines/jdk1.8.0_152.jdk/Contents/Home

我在本地将其放入到了下面的路径下了,

/Library/Java/JavaVirtualMachines/jdk1.8.0_152.jdk/Contents/Home/jre/lib/security

在完成jce的安装后,可以尝试启动配置中心。在控制台中,将会输出一些配置中心特有的端点,

08-18 14:04:43.238  INFO 53689 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/encrypt],methods=[POST]}" onto public java.lang.String org.springframework.cloud.config.server.encryption.EncryptionController.encrypt(java.lang.String,org.springframework.http.MediaType)
2017-08-18 14:04:43.238  INFO 53689 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/encrypt/{name}/{profiles}],methods=[POST]}" onto public java.lang.String org.springframework.cloud.config.server.encryption.EncryptionController.encrypt(java.lang.String,java.lang.String,java.lang.String,org.springframework.http.MediaType)
2017-08-18 14:04:43.238  INFO 53689 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/decrypt/{name}/{profiles}],methods=[POST]}" onto public java.lang.String org.springframework.cloud.config.server.encryption.EncryptionController.decrypt(java.lang.String,java.lang.String,java.lang.String,org.springframework.http.MediaType)
2017-08-18 14:04:43.238  INFO 53689 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/decrypt],methods=[POST]}" onto public java.lang.String org.springframework.cloud.config.server.encryption.EncryptionController.decrypt(java.lang.String,org.springframework.http.MediaType)
2017-08-18 14:04:43.238  INFO 53689 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/encrypt/status],methods=[GET]}" onto public java.util.Map<java.lang.String, java.lang.Object> org.springframework.cloud.config.server.encryption.E
  • /encrypt/status:查看加密功能状态的端点。
  • /key: 查看密钥的端点。
  • /encrypt: 对请求的body内容进行加密的端点。
  • /decrypt:对请求body内容进行解密的端点
  • 尝试访问/encrypt/status端点,

    http://localhost:9090/encrypt/status
    

    页面显示:

    description: "No key was installed for encryption service", status: "NO_KEY"

    该返回信息说明当前配置中心的加密功能还不能使用,因为还没有配置对应的密钥。

    可以通过encrypt.key属性在配置文件中直接指定密钥的信息(对称性密钥),比如:在application.yml中加入配置如下:

    encrypt:
      key: zhihao.miao
    

    加入了上述服务配置信息之后,重启配置中心,重新访问http://localhost:9090/encrypt/status端点

    status: "OK"

    此时,我们配置中心的加密解密功能就已经可以使用了,访问/encrypt/decrypt端点来使用加密和解密的功能,二个都是post请求,加密和解密信息都需要通过请求体来发送。

    ➜ curl  localhost:9090/encrypt -d zhihao.miao
    af9b9ea63ce1c027d78c1c3414b425ad6f0093c20c69ad144eacb5a8b4522e7c%
    
    ➜ curl localhost:9090/decrypt -d af9b9ea63ce1c027d78c1c3414b425ad6f0093c20c69ad144eacb5a8b4522e7c
    zhihao.miao%
    

    百分号是结束符。

    我们通过配置encrypt.key参数来指定密钥的实现方式采用了对称性加密。这种方式实现起来比较简单,只需要配置一个参数即可。另外,我们也可以使用环境变量ENCRYPT_KEY来进行配置,让密钥信息外部化存储。

    修改order-service服务的git仓库的生产环境的配置(application-pro.yml):

    spring:
      datasource:
        username: '{cipher}af9b9ea63ce1c027d78c1c3414b425ad6f0093c20c69ad144eacb5a8b4522e7c'
    check:
      uri: pro-1.0
    

    贴一下配置中心config-server-git的配置(application.yml):

    spring:
      application:
        name: config-server-git
      cloud:
        config:
          server:
              uri: http://git.oschina.net/zhihaomiao/{application}-config
              username: zhihao.miao
              password: 13579qwertyu
    server:
      port: 9090
    encrypt:
      key: zhihao.miao
    

    通过url地址访问:

    http://localhost:9090/master/order-service-pro.yml
    
    check:
      uri: pro-1.0
    spring:
      datasource:
        username: zhihao.miao
    

    我们发现config-server-git服务自动解密了。

    客户端服务(order-service)进行解密,配置文件(bootstrap.yml):

    spring:
      application:
        name: order-service
      cloud:
        config:
          uri: http://localhost:9090
          profile: pro
          label: master
    server:
      port: 6060
    

    定义了OrderController:

    @RestController
    @RequestMapping("/order")
    public class OrderController {
        private Logger log = LoggerFactory.getLogger(getClass());
        @Value("${spring.datasource.username}")
        private String username;
        @Value("${check.uri}")
        private String checkurl;
        @GetMapping("/index")
        public String index(){
            log.info("username="+username+",check.uri=="+username);
            return "username="+username+",check.uri==="+checkurl;
    

    url地址访问验证:

    http://localhost:6060/order/index
    

    打印结果如下:

    username=zhihao.miao,check.uri===pro-1.0
    

    发现显示的也是解密的配置。

    如果配置文件时application而不是yml加密数据就写成

    spring.datasource.username={cipher}af9b9ea63ce1c027d78c1c3414b425ad6f0093c20c69ad144eacb5a8b4522e7c
    

    非对称加密

    spring cloud config的配置中心不仅可以使用对称性加密,也可以使用非对称性加密(比如RSA密钥对)。虽然非对象加密(比如RSA密钥对)。虽然非对称性加密的密钥生成与配置相对复杂一些,但是它具有更高的安全性。

    首先,需要通过keytool工具来生成密钥对。keytool是jdk中的一个密钥和证书的管理工具。它使用户能够管理自己的公钥/私钥及相关证书,用于(通过数字签名)自我认证(用户向其他用户,服务认证自己)或数据完整性以及认证服务。在jdk1.4以后的版本中都包含了这一工具,位置在$JAVA_HOME/bin/keytool

    echo $JAVA_HOME
    /Library/Java/JavaVirtualMachines/jdk1.8.0_152.jdk/Contents/Home
    

    我自己本地在/Library/Java/JavaVirtualMachines/jdk1.8.0_152.jdk/Contents/Home/bin下。

    生成证书文件:

    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文件(证书文件)

    也可以这样生成:

    keytool -genkeypair -alias mytestkey -keyalg RSA \
      -keypass changeme -keystore server.jks -storepass letmein
              uri: http://git.oschina.net/zhihaomiao/{application}-config
              username: zhihao.miao
              password: 13579qwertyu
    server:
      port: 9090
    encrypt:
      keyStore:
        location: classpath:/server.jks
        password: letmein
        alias: mytestkey
        secret: changeme
    

    访问加密解密的端点

    ➜ curl  localhost:9090/encrypt -d zhihao.miao
    AQArpt0MuLQ4V5/BC/1YkZlUIL72o4M4VEwreFnBXXLGD7iut6KTqTItA9fkjn+XkrHBOuPp2sR7rL8gCaNROE7kqSbxhqclAM7FQv8wy1x5/TZg+QUY/WMkDas4OZmleEZbt+JpZoV/m7n+V8tJwHfgV6zoWCbMwiMjGCzlmfTqsikb0T1t4V2n3JmnAgUtZi0Ot5Gbu1HpsJ2b/YFEH0mcoM/hgJhZNoemxXWiG+vlKCk6edozv/gPm7cz+mCHOsBPf8wQt/4HDEWJPPBIGsBs/OZVi+tEaWchBzSr2CMEdzNCG9OgD/CZneAOwBl/OCCQk83edL7uviko0E9VNlWkIvSXx6TuMCRN8EIwuK6nWPY1fwZXv+GbM17DYPw4dRA=%                                                                                       
    ➜ curl localhost:9090/decrypt -d AQArpt0MuLQ4V5/BC/1YkZlUIL72o4M4VEwreFnBXXLGD7iut6KTqTItA9fkjn+XkrHBOuPp2sR7rL8gCaNROE7kqSbxhqclAM7FQv8wy1x5/TZg+QUY/WMkDas4OZmleEZbt+JpZoV/m7n+V8tJwHfgV6zoWCbMwiMjGCzlmfTqsikb0T1t4V2n3JmnAgUtZi0Ot5Gbu1HpsJ2b/YFEH0mcoM/hgJhZNoemxXWiG+vlKCk6edozv/gPm7cz+mCHOsBPf8wQt/4HDEWJPPBIGsBs/OZVi+tEaWchBzSr2CMEdzNCG9OgD/CZneAOwBl/OCCQk83edL7uviko0E9VNlWkIvSXx6TuMCRN8EIwuK6nWPY1fwZXv+GbM17DYPw4dRA=
    zhihao.miao%
    Creating a Key Store for Testing
    java keytool证书工具使用小结