Elim的博客

用来记录一些原创性的总结


Elim

Spring Cloud Config

Spring Cloud Config提供分布式配置功能,它包含Server和Client两部分。Server负责提供统一的配置信息,Client负责从Server获取相应的配置信息。Server端的配置信息支持git存储、本地文件存储、数据库等多种存储方式,默认使用git存储。

Server简介

Spring Cloud Config Server需要添加spring-cloud-config-server依赖。

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-config-server</artifactId>
</dependency>

然后需要在配置上添加@EnableConfigServer以启用Spring Cloud Config Server的支持。

@SpringBootApplication
@EnableConfigServer
public class Application {

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

}

然后需要通过spring.cloud.config.server.git.uri指定git仓库的地址,笔者下面指定的是一个本地仓库的地址。由于Spring Cloud Config Client默认会把本地的8888端口当做Spring Cloud Config Server,所以测试时可以直接指定Spring Cloud Config Server的监听端口是8888。

server.port: 8888
spring:
  cloud:
    config:
      server:
        git:
          uri: file:///D:/work/dev/git/projects/config_repo

在Git仓库的根目录下可以定义application.properties或application.yml文件,用来定义需要共享的属性,比如我们的config_repo仓库的根路径下定义了application.yml文件,其内容如下,那么Spring Cloud Config Client从Server获取属性info.foo的值时将得到bar。

info.foo: bar

Client简介

Spring Cloud Config Client需要添加spring-cloud-starter-config依赖。

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-config</artifactId>
</dependency>

添加了该依赖后Spring Cloud Config Client将进行自动配置。默认会从地址为localhost:8888的Spring Cloud Config Server获取配置信息。比如下面测试中,我们的属性infoFoo的值将来自于Spring Environment的${info.foo},它会从我们本地的Spring Cloud Config Server的获取到我们之前定义好的info.foo: bar,即获取到bar。所以下面的单元测试是会通过的。

@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class ClientTest {

  @Value("${info.foo}")
  private String infoFoo;

  @Test
  public void test() {
    Assert.assertEquals("bar", this.infoFoo);
  }

}

当需要的属性值在Spring Cloud Config Server上没有定义时,也可以从本地的资源文件中获取。比如下面代码中我们需要获取属性名为hello的属性值,远程的Spring Cloud Config Server是没有定义该属性的。当本地的application.yml中定义了hello: world时下面的单元测试也可以通过。

@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class ClientTest {

  @Value("${hello}")
  private String hello;

  @Test
  public void test() {
    Assert.assertEquals("world", this.hello);
  }

}

当本地和远程同时定义了某个属性值时远程定义的优先级更高。比如本地的application.yml中定义了info.foo: AAA,远程的application.yml中定义了info.foo: bar,则在本地注入${info.foo}时注入的是值bar

禁用Spring Cloud Config Client

如果某个时候你就希望使用本地的配置,而不希望被远程的配置覆盖,那么你可以禁用Spring Cloud Config Client的功能,通过指定spring.cloud.config.enabled=false来禁用它。但是这个属性不能直接加在application.yml中,因为应用一起来Spring Cloud Config Client就生效了,就会从远程Server和本地配置文件来获取属性了。为了在Spring Cloud Config Client生效前禁用它,我们需要在bootstrap.yml或bootstrap.properties中定义spring.cloud.config.enabled=false

spring:
  cloud:
    config:
      enabled: false

指定Spring Cloud Config Server地址

Spring Cloud Config Client默认会连接http://localhost:8888的Server,所以如果你的Server的部署地址不是http://localhost:8888,则可以通过spring.cloud.config.uri进行指定。它也必须是定义在bootstrap.yml文件中才会生效的。

spring.cloud.config.uri: http://localhost:8080

分Profile管理配置信息

我们知道Spring的配置文件是可以分Profile的,同样一个属性的值在不同的Profile下可以有不同的值,比如生产环境下它的值可以是A,测试环境下可能就是B,比较常见的就是一些IP类的地址,比如数据库连接信息。默认的application.properties文件是会加载的,当启用了名为prod的Profile时还会加载application-prod.properties文件。当在application.properties中定义了a=1,在application-prod.properties中定义了a=2时,如果启用了名为prod的Profile,那么在获取属性a时取到的将是2。使用Spring Cloud Config的时候也可以分Profile定义多个属性文件,默认的用application.properties或application.yml文件定义,特性的用application-<profile>.propertiesapplication-<profile>.yml定义,这些属性都是定义在Spring Cloud Config Server侧的,然后在Spring Cloud Config Client的bootstrap.yml中通过spring.profiles.active来指定需要启用的Profile。

spring:
  profiles:
    active: prod

如果需要同时启用多个Profile就用逗号分隔,比如下面就同时启用了profile1和profile2。

spring:
  profiles:
    active: profile1,profile2

当同时启用了多个Profile时,最后一个Profile的优先级更高。

如果在Client端使用的Profile是profile1,但是在从Server端取属性配置时希望以profile2获取,然而Client端的Profile又不能改为profile2,此时可以通过在bootstrap.yml中的spring.cloud.config.profile属性来指定从Server端获取属性配置时需要使用的Profile,即从Server端获取属性配置时spring.cloud.config.profile的值优先级高于spring.profiles.active

spring:
  cloud:
    config:
      profile: profile2

取应用特定的配置

在Client端可以通过spring.application.name指定应用的名称,当指定了spring.application.name时,Client还会从Server加载应用名称对应的yml或properties文件。比如spring.application.name=project1,则还会从Server获取定义在project1.yml文件中的属性,它的优先级比application.yml中定义的更高。它也可以和Profile一起使用,如当指定了激活的Profile是prod时,它还会从project1-prod.yml文件获取属性。

如果Client指定了spring.application.name=app,但是你又不希望它从Server端获取属性配置时按照app去获取,而更希望它按照app2去获取,但是你又不能动spring.application.name的值,此时则可以通过spring.cloud.config.name=app2来指定去Server端取属性配置时传递的应用的名称是app2。

Client配置从Server获取数据的超时时间

Client从Server获取数据是通过Http获取的,默认的超时时间是3分钟多5秒,如果你觉得这个时间太长或者太短,可以通过spring.cloud.config.requestReadTimeout进行调整,单位是毫秒。

spring:
  cloud:
    config:
      request-read-timeout: 10000

指定Client快速失败

正常情况下Spring Cloud Config Client在连接不上Spring Cloud Config Server时不会报错,它只是会取不到远程配置的属性而已。如果你希望Spring Cloud Config Client在启动时如果连接不上Spring Cloud Config Server就报错,启动失败,则可以在Client端的bootstrap.yml中配置spring.cloud.config.failFast=true

spring:
  cloud:
    config:
      fail-fast: true

Client重试

可能会有偶尔的网络波动导致连接不上Spring Cloud Config Server,为了避免这种情况,我们通常可以在Client端加入Spring Retry依赖,这样可以使用它的Retry机制。

<dependency>
  <groupId>org.springframework.retry</groupId>
  <artifactId>spring-retry</artifactId>
</dependency>

Spring Retry默认会最多重试6次,初始间隔1秒,以1.1倍递增。如果有需要可以在bootstrap.yml中进行如下配置。下面的配置定义了初始延时时间是2秒,每次以1.2倍的速度增长,最大重试间隔时间是5秒,最多重试10次。

spring:
  cloud:
    config:
      retry:
        initial-interval: 2000
        max-attempts: 10
        max-interval: 5000
        multiplier: 1.2

使用远程Git

如果希望使用远程Git仓库地址也是可以的,只需指定对应的远程仓库地址。

spring:
  cloud:
    config:
      server:
        git:
          uri: https://github.com/elim168/config_repo.git

可以通过spring.cloud.config.server.git.skipSslValidation=true来忽略SSL证书校验,默认是false。

spring:
  cloud:
    config:
      server:
        git:
          uri: https://github.com/elim168/config_repo.git
          skip-ssl-validation: true

每个应用一个Git仓库

Server端在指定Git仓库时可以使用一个特殊的占位符{application},比如下面这样。

spring:
  cloud:
    config:
      server:
        git:
          uri: https://github.com/elim168/{application}

{application}占位符的值将由Client端的spring.application.name来决定,比如客户端指定的spring.application.name的值为abc,则该Client对应的Server的远程仓库地址就是https://github.com/elim168/abc,即该Client会从https://github.com/elim168/abc这个Git仓库的application.yml等文件中获取属性。

每个Profile一个仓库

Server端的Git仓库地址还可以使用的一个特殊占位符是{profile},它的值由Client端启用的Profile决定。通过它可以实现每个Profile一个仓库,比如Server端配置Git仓库地址为https://github.com/elim168/{profile},当在Client端的bootstrap.yml文件中指定spring.profiles.activespring.cloud.config.profile的值为abc时,对应的Server端的Git仓库地址就是https://github.com/elim168/abc

每个组织一个Git仓库

Server端在指定Git仓库时可以使用一个特殊的占位符{application},比如下面这样。

spring:
  cloud:
    config:
      server:
        git:
          uri: https://github.com/{application}

{application}占位符的值将由Client端的spring.application.name来决定,此时Client端的spring.application.name的值需是organation(_)application的格式,比如客户端指定的spring.application.name的值为abc(_)def,则该Client对应的Server的远程仓库地址就是https://github.com/abc/def。这样就实现了根据组织和应用来决定Git仓库地址。

配置多仓库

Server可以同时配置多个Git仓库地址,然后通过application和profile来动态选择Git仓库,比如下面配置通过spring.cloud.config.server.git.uri指定了默认的Git仓库地址是https://github.com/elim168/config_repo,通过spring.cloud.config.server.git.repos定义了多个Git仓库,app1、app2、app3是仓库的命名,每个仓库可以通过pattern属性来指定匹配条件,它是application/profile的组合,其中profile可以省略,没有指定profile时表示匹配所有的Profile。没有指定pattern时,pattern默认取仓库的名称,比如下面的app1。当Client端指定的spring.application.nameapp1时,Server端将从https://github.com/elim168/app1这个Git仓库获取属性值;当Client端指定的spring.application.name的值是以app2开头的,比如app2、app2019等时,Server端将从https://github.com/elim168/app2这个Git仓库获取属性值;当Client指定的spring.application.name的值是以app3开头的,且指定的Profile是以dev开头的时Server端将从https://github.com/elim168/app3这个Git仓库获取属性。其它情况将从默认的https://github.com/elim168/config_repo这个Git仓库获取属性值。

spring:
  cloud:
    config:
      server:
        git:
          uri: https://github.com/elim168/config_repo
          repos:
            app1: https://github.com/elim168/app1
            app2:
              patten: app2*
              uri: https://github.com/elim168/app2
            app3:
              pattern: app3/dev*,app3*/dev*
              uri: https://github.com/elim168/app3

此种情况配置Git仓库地址时也可以使用{application}{profile}等占位符。

搜索Git仓库子目录

默认情况下Spring Cloud Config Server会在指定Git仓库的根路径寻找对应的application.yml文件。可以通过指定searchPaths来指定需要搜索的子目录。比如下面的配置除了在根路径寻找配置外,还会在app1子目录下寻找配置文件,当根路径和指定的子路径下都存在某个配置属性时,子路径下指定的优先级更高。

spring:
  cloud:
    config:
      server:
        git:
          uri: https://github.com/elim168/config_repo.git
          search-paths: app1

searchPaths也是可以同时指定多个的,对应一个数组,也可以使用通配符。

spring:
  cloud:
    config:
      server:
        git:
          uri: https://github.com/elim168/config_repo.git
          search-paths: app1,app2/*

searchPaths中也可以使用占位符,比如{application},这样也可以实现一个Git仓库同时管理多个应用的配置文件。

spring:
  cloud:
    config:
      server:
        git:
          uri: https://github.com/elim168/config_repo.git
          search-paths: '{application}'

指定使用的Git分支

Spring Cloud Config Server在使用Git仓库作为资源文件的来源时,默认会获取指定的Git仓库的master分支。可以在Server端通过spring.cloud.config.server.defaultLabel指定默认的Git分支,它对所有的Client都生效。

spring:
  cloud:
    config:
      server:
        default-label: 'dev'

如果只是某个Client需要应用特殊的Git分支,则可以在Client端的bootstrap.yml文件中通过spring.cloud.config.label指定。比如下面就指定了本Client需要使用Server端的Git仓库的dev分支。

spring:
  cloud:
    config:
      label: dev

Spring Cloud Config Server端也可以在可以使用占位符的位置使用{label}占位符,对应的取值来自客户端的spring.cloud.config.label。当服务端使用的是Git存储时,这个占位符通常用处不大,但是当服务端使用的是其它存储,比如本地文件存储时就还是有用的,因为可以通过它来区别二级目录。

关于远程仓库的克隆

默认情况下,Spring Cloud Config Server会在第一次请求获取配置信息时克隆对应的远程Git仓库到本地,可以通过其cloneOnStartup属性来指定在Server启动后就克隆。

spring:
  cloud:
    config:
      server:
        git:
          uri: https://github.com/elim168/config_repo.git
          clone-on-start: true

当本地仓库的某些文件被污染了以后,Server在从远程仓库获取更新时将会失败,可以通过指定forcePull属性为true来强制更新,此时将忽略本地仓库的变更,强制使用远程仓库的内容覆盖本地仓库。

spring:
  cloud:
    config:
      server:
        git:
          uri: https://github.com/elim168/config_repo.git
          search-paths: '{application}'
          clone-on-start: true
          force-pull: true

克隆的远程仓库默认会存放在系统临时目录下,可以通过basedir属性指定克隆的远程仓库在本地存储的路径。

spring:
  cloud:
    config:
      server:
        git:
          uri: https://github.com/elim168/config_repo.git
          basedir: /git-repo

Spring Cloud Config Server的本地仓库默认会在每次有请求需要获取配置信息时都从远程仓库同步一次最新的配置。我们可以通过refreshRate属性来配置本地仓库从远程仓库同步数据的时间间隔,单位是秒。比如下面就定义了每60秒从远程仓库同步一次。

spring:
  cloud:
    config:
      server:
        git:
          uri: https://github.com/elim168/config_repo.git
          refresh-rate: 60

Server使用本地配置文件

之前介绍的都是Spring Cloud Config Server使用Git仓库作为配置文件的来源,不管是远程仓库还是本地仓库。Spring Cloud Config Server还支持本地的非Git仓库作为配置文件的来源。使用本地配置文件时需要指定Server端的spring.profiles.active=native,然后通过spring.cloud.config.server.native.searchLocations指定本地配置文件存储的位置,默认会读取根路径和config子目录下的配置文件。比如下面的配置就定义了Spring Cloud Config Server将使用本地的D:/tmp/config目录下的application.properties、application.yml等文件作为属性来源的配置文件,也可以是D:/tmp/config/config目录下的。如果指定了激活的Profile,也会获取application-<profile>.<properties|yml>文件。

spring:
  cloud:
    config:
      server:
        native:
          search-locations: D:/tmp/config
  profiles:
    active: native

spring.cloud.config.server.native.searchLocations的值也可以使用{application}{profile}等占位符,比如下面这样。

spring:
  cloud:
    config:
      server:
        native:
          search-locations: D:/tmp/config/{application}

spring.cloud.config.server.native.searchLocations也支持同时指定多个路径,它是一个数组。

如果需要读取Spring Cloud Config Server应用本地Classpath下的内容,则可以直接配置spring.cloud.config.server.native.searchLocations的值为classpath:/xxx,比如配置都存放在应用Classpath下的configs目录,则可以配置spring.cloud.config.server.native.searchLocations=classpath:/configs。因为它不支持读取标准的Classpath下的内容(如classpath:/classpath:/config/等),所以不要指定searchLocations为Spring Cloud Config Server标准的读取配置的路径,如classpath:/classpath:/config/等。如果要使用searchLocations为classpath:/config/则可以指定Spring Cloud Config Server自身的读取配置文件的位置不包含classpath:/config/,比如只包含classpath:/。Spring Boot的配置文件路径默认是可以通过spring.config.location参数进行定义的,通过该参数可以在部署时把配置文件定义在jar包以外的文件系统,然后通过Java启动参数-Dspring.config.location来指定配置文件在文件系统的具体位置或目录。但是对于Spring Cloud Config而言,项目特定的配置如果我们是配置在Classpath下面的,想要在部署时定义在jar包以外可以通过-Dspring.cloud.config.server.native.searchLocations指定,也可以定义一个额外的参数,比如是appConfig.location,它的默认值可以是classpath:/configs,然后可以把该参数赋值给spring.cloud.config.server.native.searchLocations,然后通过Java启动参数来指定appConfig.location的值。

Server使用数据库存储

Spring Cloud Config Server也支持使用数据库作为存储。使用数据库作为存储时需要指定激活的Profile为jdbc,需要引入spring-jdbc依赖,相应的JDBC驱动。笔者使用的是MySQL,所以添加如下依赖。

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
</dependency>

然后需要基于Spring Boot自动配置机制配置数据源连接信息。

spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/test?useSSL=false
    username: root
    password: elim

使用基于数据库的Spring Cloud Config Server时,其默认会通过SELECT KEY, VALUE from PROPERTIES where APPLICATION=? and PROFILE=? and LABEL=?语句获取属性信息,所以我们需要在所使用的数据库中创建一个名为PROPERTIES的表,至少拥有APPLICATION、PROFILE、LABEL、KEY和VALUE列,类型一般都是字符串型。我们也可以通过spring.cloud.config.server.jdbc.sql属性来调整这个SQL,这样可以有不同的表名称,表结构等。笔者使用的MySQL数据库key是一个关键字,不能直接用来作为列名称,需要加上``号,所以笔者需要重写其SQL。

spring:
  cloud:
    config:
      server:
        jdbc:
          sql: 'SELECT `KEY`, VALUE from PROPERTIES where APPLICATION=? and PROFILE=? and LABEL=?'

我们可以看到默认的SQL语句需要接收3个参数,分别是应用名称,激活的Profile和LABEL,客户端不指定LABEL时,服务端默认使用master,如有需要可以在客户端通过spring.cloud.config.label来指定使用的LABEL。所以假设我们客户端的应用名是app1,激活的profile是dev,没有指定LABEL,需要获取KEY为info.foo的值,那么默认会寻找APPLICATION=app1,PROFILE=dev,LABEL=master,KEY=info.foo那一行的VALUE。那么你可能会想,它有没有像前面介绍过的几种存储一样,有获取默认属性值的机制呢?答案是有的,当指定APPLICATION的行记录不存在时,会获取APPLICATION=application的行记录,当指定PROFILE的行记录不存在时会获取PROFILE=default的行记录。所以假设我们的PROPERTIES表数据如下表所示,当客户端的应用名称是app1、激活的PROFILE是dev时,获取到的属性info.foo的值是value1;当变更PROFILE为test时,获取到的值是value2;当变更PROFILE为prod时,获取到的值是什么呢?此时获取到的会是value0。如果客户端的应用名称是app2,激活的PROFILE是dev,那么它获取到的属性info.foo的值是value3;如果它激活的PROFILE是test,那么它获取到的属性值就是value4了。

APPLICATION PROFILE LABEL KEY VALUE
app1 default master info.foo value0
app1 dev master info.foo value1
app1 test master info.foo value2
application dev master info.foo value3
application default master info.foo value4

基于JDBC的存储,其底层不是每次从数据库中取特定的Key,如你所看到的前面介绍的它获取属性的SQL那样,它是一次获取满足条件的所有的属性键值对。

同时使用多种属性来源

方式一

当需要同时使用多种属性来源时,需要指定Server端激活的Profile为composite。然后通过spring.cloud.config.server.composite指定每个属性来源的配置,每个配置需要通过type属性指定属性来源类型,然后可以指定特定属性来源类型的其它属性,比如Git可以通过uri指定Git仓库地址,jdbc通过sql指定查询的SQL语句等。下面的配置就同时组合了3种属性来源,第一个是远程Git仓库,第二个是本地Git仓库,第三个是来源于数据库。当定义了多种属性来源时,按照从上到下的定义顺序,越是定义在上面的优先级越高,且每种属性来源是解析一个属性值时是独立的,每个属性来源分别解析好属性值后,再按照从上到下的优先级顺序进行汇总。也就是说如果在第一个属性来源的application.properties中定义了info.foo=value1,再第二个属性来源中的application-dev.properties中定义了info.foo=value2,即使当前Client的激活Profile是dev,其获取到的info.foo属性的值也是value1,而不是value2

spring:
  profiles:
    active: composite
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/test?useSSL=false
    username: root
    password: elim
spring.cloud.config.server.composite:
  -
    type: git
    uri: https://github.com/elim168/config_repo.git
  -
    type: git
    uri: file:///D:/work/dev/git/projects/config_repo
  -
    type: jdbc
    sql: 'SELECT `KEY`, VALUE from PROPERTIES where APPLICATION=? and PROFILE=? and LABEL=?'

方式二

直接指定激活的Profile为需要的属性来源的类型的名称,git、native、jdbc等。下面代码就通过spring.profiles.active指定了两种属性来源,git和jdbc。然后还通过下级属性order指定了它们的优先级,数字越小的优先级越高,所以在下面配置中Git仓库中配置的属性的优先级比通过jdbc获取到的更高。方式二与方式一相比,它的缺点是不能同时指定两个同类型的属性来源。

spring:
  profiles:
    active: git,jdbc
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/test?useSSL=false
    username: root
    password: elim
  cloud:
    config:
      server:
        jdbc:
          sql: 'SELECT `KEY`, VALUE from PROPERTIES where APPLICATION=? and PROFILE=? and LABEL=?'
          order: 10
        git:
          uri: file:///D:/work/dev/git/projects/config_repo
          order: 8

Server端强制Client端共享某个属性

Server端可以配置某个属性让所有的客户端都强制共享,而忽略它们在配置的属性来源中配置的值,这是通过spring.cloud.config.server.overrides属性来配置的,其下可以配置多个需要覆盖的属性。比如下面配置中我们配置了Server端的属性来源是来自于一个本地的Git仓库,如果在本地的Git仓库的根路径下的application.yml中配置了info.foo=abc,因为我们通过spring.cloud.config.server.overrides配置了info.foo=override-bar,所以当Client端需要从Server端获取属性info.foo的值时获取到的都将是override-bar

spring:
  cloud:
    config:
      server:
        git:
          uri: file:///D:/work/dev/git/projects/config_repo
        overrides:
          info:
            foo: override-bar

对属性值加解密

Spring Cloud Config Server内部定义了一个org.springframework.cloud.config.server.encryption.EncryptionController,其定义了一些/encrypt/decrypt接口,可以对数据进行加解密。使用这些接口我们需要在Spring Cloud Config Server定义一个org.springframework.security.crypto.encrypt.TextEncryptor类型的bean。Spring Cloud中已经为我们内置了一个,可以直接在bootstrap.yml文件中定义encrypt.key属性来定义一个用来进行加密的密钥,然后Spring Cloud就会自动创建一个TextEncryptor类型的bean供加解密使用,默认使用的是基于AES算法的实现。下面代码中就指定了用于加解密的密钥是AAA。

encrypt:
  key: AAA

还可以通过encrypt.salt指定一个Salt,其默认值是deadbeef

指定了密钥后就可以通过POST方法请求Spring Cloud Config Server的/encrypt进行加密了,加密后的内容可以配置到Spring Cloud Config Server的属性值配置中,在其内容前面加上{cipher}前缀,那么当Spring Cloud Config Client来请求该属性值时,Spring Cloud Config Server发现该值是包含{cipher}前缀的,会把去掉{cipher}部分的内容进行解密后再返回给客户端。比如笔者加密了明文ABCD后的密文是14276a05d8179cec776bdf2164ca281aaef645cf30bc1b9d2f35b1e2247e5b1f,我们在Server端的属性文件中配置了属性hello.encrypt的值为该加密后的值加上{cipher}前缀。

hello.encrypt: '{cipher}14276a05d8179cec776bdf2164ca281aaef645cf30bc1b9d2f35b1e2247e5b1f'

当客户端请求属性hello.encrypt的值时将返回ABCD

可以通过spring.cloud.config.server.prefix属性指定Server端对外发布的接口的一个统一前缀。比如指定spring.cloud.config.server.prefix=/config,则加密接口对外的URL将从/encrypt变为/config/encrypt。指定了该prefix后,Client端在配置Server地址时也需要加上对应的prefix。比如原来Server端的地址是http://localhost:8080,加了前缀/config后Client端配置Server地址时需要改成http://localhost:8080/config

使用非对称加密

Spring Cloud Config Server也支持非对称加密。非对称加密需要一个KeyStore,应用如下KeyStore命令将在当前目录下生成一个使用RSA算法,别名为testkey,密钥密码为key123456,KeyStore密码为123456的KeyStore文件——server.jks文件。

keytool -genkeypair -alias testkey -keyalg RSA -dname "CN=Server1,OU=Unit,O=Elim,L=ShenZhen,S=GD,C=CN" -keypass key123456 -keystore server.jks -storepass 123456

然后把生成的server.jks文件放到Spring Cloud Config Server的Classpath下。笔者放的是Classpath根路径,所以在bootstrap.yml中进行了如下配置。这样Spring Cloud Config Server使用的加密算法就变为了非对称加密算法RSA。

encrypt:
  key-store:
    location: server.jks
    alias: testkey
    password: 123456
    secret: key123456

同样请求Server端的/encrypt加密ABCD得到的加密内容如下:

AQCcXO55WlXLEC/ZRwCENdWi2c6kqD6qqBdFO9pwXuGmR/AS3FrI8+UopZ7rfVGDev3bUDb5V4/DacTz4b+eQ7Gs3VerBg0RS6DPbK3Ypbtajjep3k3S4V1hqtSrDkEkRsT33kKbMHRiZKhwvjV+P1mIsNVwjxFpJl6qhFks9gV/oByllrHmowuAw+GS+cu2ishponaEVUJGfn+UpFQuxA/9l/ia585/4rTKLqlhzpjwDFj5o9t9iesmZ+IaoylrK7DGqK31+tO4wrv8xtpmQlCox+ykc7BbJdSh6nXeR3jC1bEJ7M8F6G1mXMRu2mKCH36Nn+MenGnUFSD7SfanIDgp/6cMmK2HfXaSr5ao+ibwyLnb1Tmjs0szn2d0tSrgWBE=

Server端使用Http Basic认证

为了安全考虑,我们的Server端可以加入认证机制,最简单的就是Http Basic认证。Spring Boot Security提供了开箱即用的Http Basic认证机制,我们可以在Server端的pom.xml中添加如下Spring Security的依赖。

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

然后可以在Server端的application.yml中添加如下配置用于指定可通过认证的用户名和密码。下面的配置指定了用户名是user,密码是password。

spring:
  security:
    user:
      name: user
      password: password

然后在Spring Cloud Config Client端需要配置用于Http Basic认证的用户名和密码。这需要配置在其bootstrap.yml文件中。

spring:
  cloud:
    config:
      uri: http://localhost:8080
      username: user
      password: password

整合使用服务发现

Spring Cloud Config还可以与Spring Cloud的服务发现机制一起使用。笔者将采用基于Eureka的实现进行介绍。当与Eureka一起使用时,那首先当然是在Spring Cloud Config Server应用中添加Eureka的依赖。

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

然后Spring Cloud Config Server应用作为Eureka的Client,需要配置Eureka Client的一些相关信息,这些信息是配置在application.yml中的。比如下面这样。

eureka:
  instance:
    prefer-ip-address: true
    instance-id: ${spring.cloud.client.hostname}:${server.port}
  client:
    service-url:
      defaultZone: http://localhost:8089/eureka/

还需要指定spring.application.name的值,它将作为Eureka注册的服务名,建议指定为configserver,因为它默认是Spring Cloud Config Client使用Spring Cloud服务发现机制时对应Spring Cloud Config Server的服务名。

Spring Cloud Config Client也需要是一个Eureka Client,所以它也需要添加Eureka Client的依赖。它的Eureka Client的相关配置信息是配置在bootstrap.yml中的。与Spring Cloud Config Server相比,它不需要作为一个服务注册到Eureka Server,所以需要指定eureka.client.registerWithEureka=false,然后不需要指定Eureka的Instance相关信息。

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8089/eureka/
    register-with-eureka: false

Spring Cloud Config Client如果需要使用服务发现机制来从Server端获取配置信息,需要指定spring.cloud.config.discovery.enabled=true。那么它默认就会从Eureka Server获取服务名为configserver的一个实例的信息,进而再访问该Spring Cloud Config Server实例获取配置的属性信息。如果Spring Cloud Config Server注册到Eureka Server的名称不是configserver,则可以通过spring.cloud.config.discovery.serviceId进行指定,比如下面就指定了Spring Cloud Config Server对应的serviceId是configserver1

spring:
  cloud:
    config:
      discovery:
        enabled: true
        service-id: configserver1

如果我们的Spring Cloud Config Server是需要进行Http Basic认证的,则我们可以通过eureka.instance.metadataMap来指定相应的用户名和密码,比如下面这样。

eureka:
  instance:
    metadata-map:
      user: user
      password: password

如果Spring Cloud Config Server是指定了spring.cloud.config.server.prefix的,则在Client端也可以通过eureka.instance.metadataMap来指定相应的前缀,这对应configPath属性,比如下面这样。

eureka:
  instance:
    metadata-map:
      configPath: /config

参考文档

(注:本文是基于Spring Cloud Finchley.SR1所写)