Spring微服务实战第3章 使用Spring Cloud配置服务器控制配置

文章目录

  • 第3章 使用Spring Cloud配置服务器控制配置
    • 3.1 管理配置(和复杂性)
      • 3.1.1 配置管理架构
      • 3.1.2 实施选择
    • 3.2 构建Spring Cloud配置服务器
      • 3.2.1 创建Spring Cloud Config引导类
      • 3.2.2 使用带有文件系统的Spring Cloud配置服务器
    • 3.3 将Spring Cloud Config与Spring Boot客户端集成
      • 3.3.1 建立许可证服务对Spring Cloud Config服务器的依赖
      • 3.3.2 配置许可证服务以使用Spring Cloud Config
      • 3.3.3 使用Spring Cloud配置服务器连接数据源
      • 3.3.4 使用@Value 注解直接读取属性
      • 3.3.5 使用Spring Cloud配置服务器和Git
      • 3.3.6 使用Spring Cloud配置服务器刷新属性
    • 3.4 保护敏感的配置信息
      • 3.4.1 下载并安装加密所需的Oracle JCE jar
      • 3.4.2 创建加密密钥
      • 3.4.3 加密和解密属性
      • 3.4.4 配置微服务以在客户端使用加密
    • 3.5 最后的想法
    • 3.6 小结
    • 源码地址
    • 3.5 最后的想法
    • 3.6 小结
    • 源码地址

第3章 使用Spring Cloud配置服务器控制配置

本章主要内容

  • 将服务配置与服务代码分开
  • 配置Spring Cloud配置服务器
  • 集成Spring Boot微服务
  • 加密敏感属性

在某种程度上来说,开发人员是被迫将配置信息与他们的代码分开的。毕竟,自上学以来,他们就一直被灌输不要将硬编码带入应用程序代码中的观念。许多开发人员在应用程序中使用一个常量类文件来帮助将所有配置集中在一个地方。将应用程序配置数据直接写入代码中通常是有问题的,因为每次对配置进行更改时,应用程序都必须重新编译和重新部署。为了避免这种情况,开发人员会将配置信息与应用程序代码完全分离。这样就可以很容易地在不进行重新编译的情况下对配置进行更改,但这样做也会引入复杂性,因为现在存在另一个需要与应用程序一起管理和部署的制品。

许多开发人员转向低层级的属性文件(即YAML、JSON或XML)来存储他们的配置信息。这份属性文件存放在服务器上,通常包含数据库和中间件连接信息,以及驱动应用程序行为的相关元数据。将应用程序分离到一个属性文件中是很简单的,除了将配置文件放在源代码控制下(如果需要这样做的话),并将配置文件部署为应用程序的一部分,大多数开发人员永远不会再对应用程序配置进行实施。

这种方法可能适用于少量的应用程序,但是在处理可能包含数百个微服务的基于云的应用程序,其中每个微服务可能会运行多个服务实例时,它会迅速崩溃。

配置管理突然间变成一件重大的事情,因为在基于云的环境中,应用程序和运维团队必须与配置文件的“鼠巢”进行斗争。基于云的微服务开发强调以下几点。

(1)应用程序的配置与正在部署的实际代码完全分离。

(2)构建服务器、应用程序以及一个不可变的镜像,它们在各环境中进行提升时永远不会发生变化。

(3)在服务器启动时通过环境变量注入应用程序配置信息,或者在微服务启动时通过集中式存储库读取应用程序配置信息。

本章将介绍在基于云的微服务应用程序中管理应用程序配置数据所需的核心原则和模式。

3.1 管理配置(和复杂性)

对于在云中运行的微服务,管理应用程序配置是至关重要的,因为微服务实例需要以最少的人为干预快速启动。每当人们需要手动配置或接触服务以实现部署时,都有可能出现配置漂移、意外中断以及应用程序响应可伸缩性挑战出现延迟的情况。

通过建立要遵循的4条原则,我们来开始有关应用程序配置管理的讨论。

(1)分离——我们希望将服务配置信息与服务的实际物理部署完全分开。应用程序配置不应与服务实例一起部署。相反,配置信息应该作为环境变量传递给正在启动的服务,或者在服务启动时从集中式存储库中读取。

(2)抽象——将访问配置数据的功能抽象到一个服务接口中。应用程序使用基于REST的JSON服务来检索配置数据,而不是编写直接访问服务存储库的代码(也就是从文件或使用JDBC从数据库读取数据)。

(3)集中——因为基于云的应用程序可能会有数百个服务,所以最小化用于保存配置信息的不同存储库的数量至关重要。将应用程序配置集中在尽可能少的存储库中。

(4)稳定——因为应用程序的配置信息与部署的服务完全隔离并集中存放,所以不管采用何种方案实现,至关重要的一点就是保证其高可用和冗余。

要记住一个关键点,将配置信息与实际代码分开之后,开发人员将创建一个需要进行管理和版本控制的外部依赖项。我总是强调应用程序配置数据需要跟踪和版本控制,因为管理不当的应用程序配置很容易滋生难以检测的bug和计划外的中断。

偶发复杂性

我亲身体验过缺乏管理应用程序配置数据策略的危险。在某家财富500强金融服务公司工作期间,我被要求帮助一个大型WebSphere升级项目回到正轨。该公司在WebSphere上有超过120个应用程序,并且该公司需要在整个应用程序环境在供应商的维护期终止之前,将其基础设施从WebSphere 6升级到WebSphere 7。

这个项目已经进行了1年,花费了100万美元的人力和硬件成本,只部署了120个应用程序中的1个。按照目前的轨迹,还需要两年时间才能完成升级。

当我开始与应用程序团队一起工作时,我发现的一个主要问题(也是仅有的一个问题),应用程序团队在属性文件中管理其数据库的所有配置以及其服务的端点。这些属性文件是手工管理的,不受源代码控制。120个应用程序分布在4个环境中,每个应用程序有多个WebSphere节点,这个配置文件的“鼠巢”导致团队试图迁移12 000(你没有看错,确实是12 000)个配置文件,这些配置文件散落在数百个服务器以及运行在服务器上的应用程序中。这些文件仅用于应用程序的配置,甚至不包括应用程序服务器的配置。

我说服项目发起人,用两个月的时间将所有应用程序信息整合到具有20个配置文件的集中版本控制的配置库中。当我询问框架团队究竟是怎么形成这12 000个配置文件的境况时,该团队的首席工程师说,最初他们围绕一小部分应用程序设计了配置策略,但构建和部署的Web应用程序的数量在5年内爆炸式增长,尽管他们申请资金和时间来重新设计配置管理方法,但他们的业务合作伙伴和IT领导者从未将这件事视为优先事项。

不花时间来弄清楚如何进行配置管理可能会产生实实在在(而且代价昂贵)的下游影响。

3.1.1 配置管理架构

从第2章中可以看出,微服务配置管理的加载发生在微服务的引导阶段。作为回顾,图3-1展示了微服务生命周期。

image-20210901155626115

图3-1 应用程序配置数据在服务引导阶段被读取

我们先来看一下之前在3.1节中提到的4条原则(分离、抽象、集中和稳定),看看这4条原则在服务引导时是如何应用的。图3-2更详细地探讨了引导过程,并展示了配置服务在此步骤中扮演的关键角色。

image-20210901155649080

在图3-2中,发生了以下几件事情。

(1)当一个微服务实例出现时,它将调用一个服务端点来读取其所在环境的特定配置信息。配置管理的连接信息(连接凭据、服务端点等)将在微服务启动时被传递给微服务。

(2)实际的配置信息驻留在存储库中。基于配置存储库的实现,可以选择使用不同的实现来保存配置数据。配置存储库的实现选择可以包括源代码控制下的文件、关系数据库或键值数据存储。

(3)应用程序配置数据的实际管理与应用程序的部署方式无关。配置管理的更改通常通过构建和部署管道来处理,其中配置的更改可以通过版本信息进行标记,并通过不同的环境进行部署。

(4)进行配置管理更改时,必须通知使用该应用程序配置数据的服务,并刷新应用程序数据的副本。

现在,我们已经完成了概念架构,这个概念架构阐示了配置管理模式的各个组成部分,以及这些部分如何组合在一起。我们现在要继续看看这些模式的不同解决方案,然后看一下具体的实现。

3.1.2 实施选择

幸运的是,开发人员可以在大量久经测试的开源项目中进行选择,以实施配置管理解决方案。我们来看一下几个不同的方案选择,并对它们进行比较。表3-1列出了这些方案选择。

表3-1 用于实施配置管理系统的开源项目

项目名称 描  述 特  点
Etcd 使用Go开发的开源项目,用于服务发现和键值管理,使用raft协议作为它的分布式计算模型 非常快和可伸缩 可分布式 命令行驱动 易于搭建和使用
Eureka 由Netflix开发。久经测试,用于服务发现和键值管理 分布式键值存储 灵活,需要费些功夫去设置 提供开箱即用的动态客户端刷新
Consul 由Hashicorp开发,特性上类似于Etcd和Eureka,它的分布式计算模型使用了不同的算法(SWIM协议) 快速 提供本地服务发现功能,可直接与DNS集成 没有提供开箱即用的动态客户端刷新
ZooKeeper 一个提供分布式锁定功能的Apache项目,经常用作访问键值数据的配置管理解决方案 最古老的、最久经测试的解决方案 使用最为复杂 可用作配置管理,但只有在其他架构中已经使用了ZooKeeper的时候才考虑使用它
Spring Cloud Config 一个开源项目,提供不同后端支持的通用配置管理解决方案。它可以将Git、Eureka和Consul作为后端进行整合 非分布式键值存储 提供了对Spring和非Spring服务的紧密集成 可以使用多个后端来存储配置数据,包括共享文件系统、Eureka、Consul和Git

表3-1中的所有方案都可以轻松用于构建配置管理解决方案。对于本章和本书其余部分的示例,都将使用Spring Cloud配置服务器。选择这个解决方案出于多种原因,其中包括以下几个。

(1)Spring Cloud配置服务器易于搭建和使用。

(2)Spring Cloud配置与Spring Boot紧密集成。开发人员可以使用一些简单易用的注解来读取所有应用程序的配置数据。

3)Spring Cloud配置服务器提供多个后端用于存储配置数据。如果读者已经使用了Eureka和Consul等工具,那么可以将它们直接插入Spring Cloud配置服务器中。

(4)在表3-1所示的所有解决方案中,Spring Cloud配置服务器可以直接与Git源控制平台集成。Spring Cloud配置与Git的集成消除了解决方案的额外依赖,并使版本化应用程序配置数据成为可能。

其他工具(Etcd、Consul、Eureka)不提供任何类型的原生版本控制,如果开发人员想要版本控制的话,则必须自己去建立它。如果读者使用Git,那么使用Spring Cloud配置服务器是一个很有吸引力的选择。

对于本章的其余部分,我们将要完成以下几项工作。

(1)创建一个Spring Cloud配置服务器,并演示两种不同的机制来提供应用程序配置数据,一种使用文件系统,另一种使用Git存储库。

(2)继续构建许可证服务以从数据库中检索数据。

(3)将Spring Cloud配置服务挂钩(hook)到许可证服务,以提供应用程序配置数据。

3.2 构建Spring Cloud配置服务器

Spring Cloud配置服务器是基于REST的应用程序,它建立在Spring Boot之上。Spring Cloud配置服务器不是独立服务器,相反,开发人员可以选择将它嵌入现有的Spring Boot应用程序中,也可以在嵌入它的服务器中启动新的Spring Boot项目。

首先需要做的是建立一个名为confsvr的新项目目录。在confsvr目录中创建一个新的Maven文件,该文件将用于拉取启动Spring Cloud配置服务器所需的JAR文件。代码清单3-1列出的是关键部分,而不是整个Maven文件。

代码清单3-1 为Spring Cloud配置服务器创建pom.xml


<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://
    ➥  maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0modelVersion>

  <groupId>com.thoughtmechanixgroupId>
  <artifactId>configurationserverartifactId>
  <version>0.0.1-SNAPSHOTversion>
  <packaging>jarpackaging>

  <name>Config Servername>
  <description>Config Server demo projectdescription>

  <parent>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-parentartifactId>
    <version>1.4.4.RELEASEversion>    ⇽---  将要使用的Spring Boot版本
  parent>
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework.cloudgroupId>
        <artifactId>spring-cloud-dependenciesartifactId>
        <version>Camden.SR5version>    ⇽---  将要使用的Spring Cloud版本
        <type>pomtype>
        <scope>importscope>
      dependency>
    dependencies>
  dependencyManagement>

  <properties>
    <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
    <start-class>com.thoughtmechanix.confsvr.
    ➥  ConfigServerApplication start-class>    ⇽---  配置服务器将要使用的引导类
    <java.version>1.8java.version>
    <docker.image.name>johncarnell/tmx-confsvrdocker.image.name>
    <docker.image.tag>chapter3docker.image.tag>
  properties>

  <dependencies>
    <dependency>
      <groupId>org.springframework.cloudgroupId>    ⇽---  在这个特定服务中将要使用的 Spring Cloud项目
      <artifactId>spring-cloud-starter-configartifactId>
    dependency>

    <dependency>
      <groupId>org.springframework.cloudgroupId>    ⇽---在这个特定服务中将要使用的 Spring Cloud项目
      <artifactId>spring-cloud-config-serverartifactId>
    dependency>
  dependencies>


project>

在代码清单3-1所示的Maven文件中,首先声明了要用于微服务的Spring Boot版本(1.4.4版本)。下一个重要的Maven定义部分是将要使用的Spring Cloud Config父物料清单(Bill of Materials,BOM)。Spring Cloud是一个大量独立项目的集合,这些项目全部遵循自身的发行版本而更新。此父BOM包含云项目中使用的所有第三方库和依赖项以及构成该版本的各个项目的版本号。在这个例子中,我们使用Spring Cloud的Camden.SR5版本。通过使用BOM定义,可以保证在Spring Cloud中使用子项目的兼容版本。这也意味着不必为子依赖项声明版本号。代码清单3-1的剩余部分负责声明将在服务中使用的特定Spring Cloud依赖项。第一个依赖项是所有Spring Cloud项目使用的spring-cloud-starter-config。第二个依赖项是spring- cloud-config-server起步项目,它包含了spring-cloud-config-server的核心库。

来吧,坐上发行版系列的列车

Spring Cloud使用非传统机制来标记Maven项目。Spring Cloud是独立子项目的集合。Spring Cloud团队通过其称为“发行版系列”(release train)的方式进行版本发布。组成Spring Cloud的所有子项目都包含在一个Maven物料清单(BOM)中,并作为一个整体进行发布。Spring Cloud团队一直使用伦敦地铁站的名称作为他们发行版本的名称,每个递增的主要版本都按字母表从小到大的顺序赋予一个伦敦地铁站的站名。目前已有3个版本,即Angel、Brixton和Camden。Camden是迄今为止最新的发行版,但是它的子项目中仍然有多个候选版本分支。

需要注意的是,Spring Boot是独立于Spring Cloud发行版系列发布的。因此,Spring Boot的不同版本可能与Spring Cloud的不同版本不兼容。参考Spring Cloud网站,可以看到Spring Boot和Spring Cloud之间的版本依赖项,以及发行版系列中包含的不同子项目版本。

我们仍然需要再多创建一个文件来让核心配置服务器正常运行。这个文件是位于confsvr/src/ main/resources目录中的application.yml文件。application.yml文件告诉Spring Cloud配置服务要侦听哪个端口以及在哪里可以找到提供配置数据的后端。

我们马上就能启动Spring Cloud配置服务了。现在,需要将服务器指向保存配置数据的后端存储库。对于本章,读者将要使用第2章中开始构建的许可证服务作为使用Spring Cloud Config的示例。简单起见,我们将为以下3个环境创建配置数据:在本地运行服务时的默认环境、开发环境以及生产环境。

在Spring Cloud配置中,一切都是按照层次结构进行的。应用程序配置由应用程序的名称表示。我们为需要拥有配置信息的每个环境提供一个属性文件。在这些环境中,我们将创建两个配置属性:

  • 由许可证服务直接使用的示例属性;
  • 用于存储许可证服务数据的Postgres数据库的配置。

图3-3阐述了如何创建和使用Spring Cloud配置服务。需要注意的是,在构建配置服务时,它将成为在环境中运行的另一个微服务。一旦建立配置服务,服务的内容就可以通过基于HTTP的REST端点进行访问。

image-20210901160235660

图3-3 Spring Cloud配置将环境特定的属性公开为基于HTTP的端点

应用程序配置文件的命名约定是“应用程序名称-环境名称.yml”。从图3-3可以看出,环境名称直接转换为可以浏览配置信息的 URL。随后,启动许可证微服务示例时,要运行哪个服务环境是由在命令行服务启动时传入的 Spring Boot 的 profile 指定的。如果在命令行上没有传入profile,Spring Boot将始终默认加载随应用程序打包的application.yml文件中的配置数据。

以下是为许可证服务提供的一些应用程序配置数据的示例。这些数据包含在 confsvr/src/ main/resources/config/licensingservice/licensingservice.yml文件中,图3-3引用了这些数据。下面是此文件的一部分内容:

tracer.property: "I AM THE DEFAULT"
spring.jpa.database: "POSTGRESQL"
spring.datasource.platform:  "postgres"
spring.jpa.show-sql: "true"
spring.database.driverClassName: "org.postgresql.Driver"
spring.datasource.url: "jdbc:postgresql://database:5432/eagle_eye_local"
spring.datasource.username: "postgres"
spring.datasource.password: "p0stgr@s"
spring.datasource.testWhileIdle: "true"
spring.datasource.validationQuery: "SELECT 1"
spring.jpa.properties.hibernate.dialect:
➥  "org.hibernate.dialect.PostgreSQLDialect"

在实施前想一想

我建议不要在中大型云应用中使用基于文件系统的解决方案。使用文件系统方法,意味着要为想要访问应用程序配置数据的所有云配置服务器实现共享文件挂载点。在云中创建共享文件系统服务器是可行的,但它将维护此环境的责任放在开发人员身上。

我将展示如何以文件系统作为入门使用Spring Cloud配置服务器的最简单示例。在后面的几节中,我将介绍如何配置Spring Cloud配置服务器以使用基于云的Git供应商(如Bitbucket或GitHub)来存储应用程序配置。

3.2.1 创建Spring Cloud Config引导类

本书涵盖的每一个Spring Cloud服务都需要一个用于启动该服务的引导类。这个引导类包含两样东西:作为服务启动入口点的Java main()方法,以及一组告诉启动的服务将要启动哪种类型的Spring Cloud行为的Spring Cloud注解。

代码清单3-2展示了用作配置服务的引导类Application(在 confsvr/src/main/java/com/ thoughtmechanix/confsvr/Application.java中)。

代码清单3-2 Spring Cloud Config服务器的引导类

package com.thoughtmechanix.confsvr;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;

@SpringBootApplication---  Spring Cloud Config服务是Spring Boot应用程序,因此需要用@SpringBoot Application进行标记
@EnableConfigServer---  @EnableConfigServer使服务成为Spring Cloud Config服务
public class ConfigServerApplication {

    public static void main(String[] args) {---  main方法启动服务并启动Spring容器
        SpringApplication.run(ConfigServerApplication.class, args);
    }
}

接下来,我们将使用最简单的文件系统示例来搭建Spring Cloud配置服务器。

3.2.2 使用带有文件系统的Spring Cloud配置服务器

Spring Cloud配置服务器使用confsvr/src/main/resources/application.yml文件中的条目指向要保存应用程序配置数据的存储库。创建基于文件系统的存储库是实现这一目标的最简单方法。

为此,要将以下信息添加到配置服务器的application.yml文件中。代码清单3-3展示Spring Cloud配置服务器的application.yml文件的内容。

代码清单3-3 Spring Cloud配置的application.yml文件

server:
  port: 8888    ⇽---  Spring Cloud配置服务器将要监听的端口
spring:
  profiles:
    active: native    ⇽---  用于存储配置的后端存储库(文件系统)
  cloud:
    config:
      server:
        native:
          searchLocations: file:///Users/johncarnell1/book/spmia-code
          /chapter3-code/confsvr/src/main/resources/config/
           licensingservice    ⇽---  配置文件存储位置的路径

在代码清单3-3所示的配置文件中,首先告诉配置服务器,对于所有配置信息的请求,应该监听哪个端口号:

server:
  port: 8888

因为我们正在使用文件系统来存储应用程序配置信息,所以需要告诉Spring Cloud配置服务器以“native”profile运行:

profiles:
  active: native

application.yml文件的最后一部分为Spring Cloud配置提供了应用程序数据所在的文件目录:

server:
  native:
    searchLocations: file:///Users/johncarnell1/book/spmia_code
    /chapter3-code/confsvr/src/main/resources/config/licensingservice

配置条目中的重要参数是searchLocations属性。这个属性为每一个应用程序提供了用逗号分隔的文件夹列表,这些文件夹含有由配置服务器管理的属性。在上一个示例中,只配置了许可证服务。

注意

如果使用Spring Cloud Config的本地文件系统版本,那么在本地运行代码时,需要修改spring.cloud.config.server.native.searchLocations属性以反映本地文件路径。

我们现在已经完成了足够多的工作来启动配置服务器。接下来,我们就使用 mvn spring-boot:run命令启动配置服务器。服务器现在应该在命令行上出现一个Spring Boot启动画面。如果用浏览器访问http://localhost:8888/licensingservice/default,那么将会看到JSON净荷与licensingservice.yml文件中包含的所有属性一起返回。图3-4展示了调用此端点的结果。

image-20210901161648532

图3-4 检索许可证服务的默认配置信息

如果读者想要查看基于开发环境的许可证服务的配置信息,可以对http://localhost: 8888/licensingservice/dev端点发起GET请求。图3-5展示了调用此端点的结果。

image-20210901162431663

图3-5 使用开发环境profile检索许可证服务的配置信息

如果仔细观察,读者会看到在访问开发环境端点时,将返回许可证服务的默认配置属性以及开发环境下的许可证服务配置。Spring Cloud配置返回两组配置信息的原因是,Spring框架实现了一种用于解析属性的层次结构机制。当Spring框架执行属性解析时,它将始终先查找默认属性中的属性,然后用特定环境的值(如果存在)去覆盖默认属性。

具体来说,如果在licensingservice.yml文件中定义一个属性,并且不在任何其他环境配置文件(如licensingservice-dev.yml)中定义它,则Spring框架将使用这个默认值。

注意

这不是直接调用Spring Cloud配置REST端点所看到的行为。REST端点将返回调用的默认值和环境特定值的所有配置值。

让我们看看如何将Spring Cloud配置服务器挂钩到许可证微服务。

3.3 将Spring Cloud Config与Spring Boot客户端集成

在上一章中,我们构建了一个简单的许可证服务框架,这个框架只是返回一个代表数据库中单个许可记录的硬编码Java对象。在下一个示例中,我们将构建许可证服务,并与持有许可数据的Postgres数据库进行交流。

我们将使用Spring Data与数据库进行通信,并将数据从许可证表映射到保存数据的POJO。数据库连接和一条简单的属性将从Spring Cloud配置服务器中读出。图3-6展示了许可证服务和Spring Cloud配置服务之间的交互。

image-20210901162537974

图3-6 使用开发环境profile检索配置信息

当许可证服务首次启动时,将通过命令行传递两条信息:Spring的profile和许可证服务用于与Spring Cloud配置服务通信的端点。Spring的profile值映射到为Spring服务检索属性的环境。当许可证服务首次启动时,它将通过从Spring的profile传入构建的端点与Spring Cloud Config服务进行联系。然后,Spring Cloud Config服务将会根据URI上传递过来的特定Spring profile,使用已配置的后端配置存储库(文件系统、Git、Consul或Eureka)来检索相应的配置信息,然后将适当的属性值传回许可证服务。接着,Spring Boot框架将这些值注入应用程序的相应部分。

3.3.1 建立许可证服务对Spring Cloud Config服务器的依赖

让我们把焦点从配置服务器转移到许可证服务。我们需要做的第一件事,就是在许可证服务中为Maven文件添加更多的条目。代码清单3-4展示了需要添加的条目。

代码清单3-4 许可证服务所需的其他Maven依赖项

<dependency>
  <groupId>org.springframework.bootgroupId>
  <artifactId>spring-boot-starter-data-jpaartifactId>    ⇽---  告诉Spring Boot将要在服务中使用Java Persistence API(JPA)
dependency>

<dependency>
  <groupId>postgresqlgroupId>
  <artifactId>postgresqlartifactId>    ⇽---  告诉Spring Boot拉取Postgres JDBC驱动程序
  <version>9.1-901.jdbc4version>
dependency>

<dependency>
  <groupId>org.springframework.cloudgroupId>
  <artifactId>spring-cloud-config-clientartifactId>    ⇽---  告诉Spring Boot拉取Spring Cloud Config客户端所需的所有依赖项
dependency>

第一个和第二个依赖项spring-boot-starter-data-jpapostgresql导入了Spring Data Java Persistence API(JPA)和Postgres JDBC驱动程序。最后一个依赖项是spring-cloud-config-client,它包含与Spring Cloud配置服务器交互所需的所有类。

3.3.2 配置许可证服务以使用Spring Cloud Config

在定义了Maven依赖项后,需要告知许可证服务在哪里与Spring Cloud配置服务器进行联系。在使用Spring Cloud Config的Spring Boot服务中,配置信息可以在bootstrap.yml和application.yml这两个配置文件之一中设置。

在其他所有配置信息被使用之前,bootstrap.yml文件要先读取应用程序属性。一般来说,bootstrap.yml文件包含服务的应用程序名称、应用程序profile和连接到Spring Cloud Config服务器的URI。希望保留在本地服务(而不是存储在Spring Cloud Config中)的其他配置信息,都可以在服务中的application.yml文件中进行本地设置。通常情况下,即使Spring Cloud Config服务不可用,我们也会希望存储在application.yml 文件中的配置数据可用。bootstrap.yml 和application.yml保存在项目的src/main/resources文件夹中。

要使许可证服务与Spring Cloud Config服务进行通信,需要添加一个licensing-service/src/ main/resources/bootstrap.yml文件,并设置3个属性,即spring.application.namespring. profiles.activespring.cloud.config.uri

代码清单3-5展示了许可证服务的bootstrap.yml文件。

代码清单3-5 配置许可证服务的bootstrap.yml文件

spring:
  application:
    name: licensingservice    ⇽---  指定许可证服务的名称,以便Spring Cloud Config客户端知道正在查找哪个服务
  profiles:
    active:
      default    ⇽---  指定服务应该运行的默认profile oprofile映射到环境
  cloud:
    config:  
      uri: http://localhost:8888    ⇽---  指定Spring Cloud Config服务器的位置

注意

Spring Boot应用程序支持两种定义属性的机制:YAML(Yet another Markup Language)和使用“.”分隔的属性名称。我们选择YAML作为配置应用程序的方法。YAML属性值的分层格式直接映射到spring.application.namespring.profiles.activespring.cloud.config.uri名称。

spring.application.name是应用程序的名称(如licensingservice)并且必须直接映射到Spring Cloud配置服务器中的目录的名称。对于许可证服务,需要在Spring Cloud配置服务器上有一个名为licensingservice的目录。

第二个属性spring.profiles.active用于告诉Spring Boot应用程序应该运行哪个profile。profile是区分Spring Boot应用程序要使用哪个配置数据的机制。对于许可证服务的profile,我们将支持服务的环境直接映射到云配置环境中。例如,通过作为profile传入开发环境,Spring Cloud配置服务器将使用开发环境的属性。如果没有设置profile,许可证服务将使用默认profile。

第三个也是最后一个属性spring.cloud.config.uri是许可证服务查找Spring Cloud配置服务器端点的位置。在默认情况下,许可证服务将在http://localhost:8888上查找配置服务器。在本章的后面,读者将看到如何在应用程序启动时覆盖boostrap.yml和application.yml文件中定义的不同属性,这样可以告知许可证微服务应该运行哪个环境。

现在,如果启动Spring Cloud配置服务,并在本地计算机上运行相应的Postgres数据库,那么就可以使用默认profile启动许可证服务。这可以通过切换到许可证服务的目录并执行以下命令来完成:

mvn spring-boot: run

通过运行此命令而不设置任何属性,许可证服务器将自动尝试使用端点(http://localhost: 8888)和在许可证服务的bootstrap.yml文件中定义的活跃profile(默认),连接到Spring Cloud配置服务器。

如果要覆盖这些默认值并指向另一个环境,可以通过将许可证服务项目编译到JAR,然后使用-D系统属性来运行这个JAR来实现。下面的命令行演示了如何使用非默认profile启动许可证服务:

java  -Dspring.cloud.config.uri=http://localhost:8888 \
      -Dspring.profiles.active=dev \
      -jar target/licensing-service-0.0.1-SNAPSHOT.jar

使用上述命令行将覆盖两个参数,即spring.cloud.config.urispring.profiles. active。使用-Dspring.cloud.config.uri=http://localhost:8888系统属性将指向一个本地运行的配置服务器。

注意

如果读者尝试从自己的台式机上使用上述的Java命令来运行从本章的GitHub存储库下载的许可证服务,将会运行失败,这是因为没有运行桌面Postgres服务器,并且GitHub存储库中的源代码在配置服务器上使用了加密。本章稍后将介绍加密。前面的例子演示了如何通过命令行来覆盖Spring属性。

使用-Dspring.profiles.active=dev系统属性,可以告诉许可证服务使用开发环境profile(从配置服务器读取),从而连接到开发环境的数据库的实例。

使用环境变量传递启动信息

在这些示例中,将这些值硬编码传递给-D参数值。在云中所需的大部分应用程序配置数据都将位于配置服务器中。但是,对于启动服务所需的信息(如配置服务器的数据),则需要启动VM实例或Docker容器并传入环境变量。

本书每章的所有代码示例都可以在Docker容器中完全运行。使用Docker,我们可以通过特定环境的Docker-compose文件来模拟不同的环境,从而协调所有服务的启动。容器所需的特定环境值作为环境变量传递到容器。例如,要在开发环境中启动许可证服务,docker/dev/docker-compose.yml文件要包含以下用于许可证服务的条目:

licensingservice:
  image: ch3-thoughtmechanix/licensing-service
  ports:
    - "8080:8080"
  environment:---  指定许可证服务容器的环境变量的开始
    PROFILE: "dev"    ⇽---  PROFILE环境变量被传递给Spring Boot服务命令行,告诉Spring Boot应该运行哪个profile
    CONFIGSERVER_URI: http://configserver:8888    ⇽---  配置服务的端点
    CONFIGSERVER_PORT: "8888"
    DATABASESERVER_PORT: "5432"

该文件中的环境条目包含两个变量PROFILE的值,这是许可证服务将要运行的Spring Boot profile。CONFIGSERVER_URI被传递给许可证服务,该属性定义了Spring Cloud配置服务器实例的地址,服务将从该URI读取其配置数据的。

在由容器运行的启动脚本中,我们将这些环境变量以-D参数传递到启动应用程序的JVM。在每个项目中,可以制作一个Docker容器,然后该Docker容器使用启动脚本启动该容器中的软件。对于许可证服务,容器中的启动脚本位于licensing-service/src/main/docker/run.sh中。在run.sh脚本中,以下条目负责启动许可证服务的JVM:

echo  "********************************************************"
echo  "Starting License Server with Configuration Service :
      $CONFIGSERVER_URI";
echo  "********************************************************"
java -Dspring.cloud.config.uri=$CONFIGSERVER_URI
-Dspring.profiles.active=$PROFILE -jar /usr/local/licensingservice/
     licensing-service-0.0.1-SNAPSHOT.jar

因为我们是通过 Spring Boot Actuator 来增强服务的自我检查能力的,所以可以通过访问http://localhost:8080/env来确认正在运行的环境。/env端点将提供有关服务的配置信息的完整列表,包括服务启动的属性和端点,如图3-7所示。

image-20210901163553757

图3-7 可以通过调用/env端点来检查许可证服务加载的配置

图3-7中要注意的关键是,许可证服务的活跃profile是dev。通过观察返回的JSON,还可以看到被返回的Postgres数据库URI是开发环境URI:jdbc:postgresgl://database: 5432/eagle-ege-dev

暴露太多的信息

围绕如何为服务实现安全性,每个组织都会有自己的规则。许多组织认为,服务不应该广播任何有关自己的信息,也不允许像/env端点这样的东西在服务上存在,因为他们相信(这是理所当然的)这样会为潜在的黑客提供太多的信息。Spring Boot为配置Spring Actuator端点返回的信息提供了丰富的功能,这些知识超出了本书的范围。Craig Walls的优秀著作《Spring Boot实战》详细介绍了这个主题,我强烈建议读者回顾一下企业安全策略并阅读Walls的书,以便能够提供想通过Spring Actuator公开的正确级别的细节。

3.3.3 使用Spring Cloud配置服务器连接数据源

至此我们已将数据库配置信息直接注入微服务中。数据库配置设置完毕后,配置许可证微服务就变成使用标准Spring组件来构建和从Postgres数据库中检索数据的练习。许可证服务已被重构成不同的类,每个类都有各自独立的职责。这些类如表3-2所示。

表3-2 许可证服务的类及其所在位置

类  名 位  置
License licensing-service/src/main/java/com/thoughtmechanix/licenses/model
LicenseRepository licensing-service/src/main/java/com/thoughtmechanix/licenses/repository
LicenseService licensing-service/src/main/java/com/thoughtmechanix/licenses/services

License类是模型类,它将持有从许可数据库检索的数据。代码清单3-6展示了License类的代码。

代码清单3-6 单个许可证记录的JPA模型代码

package com.thoughtmechanix.licenses.model;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity---  @Entity注解告诉Spring这是一个JPA类
@Table(name = "licenses")---  @Table映射到数据库的表
public class License{
    @Id---  @Id将该字段标记为主键
    @Column(name = "license_id", nullable = false)---  @Column 将该字段映射到特定数据库表中的列
    private String licenseId;

    @Column(name = "organization_id", nullable = false)
    private String organizationId;

    @Column(name = "product_name", nullable = false)
    private String productName;

    /*为了简洁,省略了其余的代码*/
}

这个类使用了多个Java持久性注解(Java Persistence Annotations,JPA),帮助Spring Data框架将Postgres数据库中的licenses表中的数据映射到Java对象。@Entity注解让Spring知道这个Java POJO将要映射保存数据的对象。@Table注解告诉Spring JPA应该映射哪个数据库表。@Id注解标识数据库的主键。最后,数据库中的每一列将被映射到由@Column标记的各个属性。

Spring Data和JPA框架提供访问数据库的基本CRUD方法。如果要构建其他方法,可以使用Spring Data存储库接口和基本命名约定来进行构建。Spring将在启动时从Repository接口解析方法的名称,并将它们转换为基于名称的SQL语句,然后在幕后生成一个动态代理类来完成这项工作。代码清单3-7展示了许可证服务的存储库。

代码清单3-7 LicenseRepository接口定义查询方法

package com.thoughtmechanix.licenses.repository;

import com.thoughtmechanix.licenses.model.License;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository---  告诉Spring Boot这是一个JPA存储库类
public interface LicenseRepository
    extends CrudRepository<License,String>---  定义正在扩展Spring CrudRepository
{
    public List<License> findByOrganizationId
    ➥(String organizationId);---  每个查询方法被Spring解析为SELECT...FROM查询
    public License findByOrganizationIdAndLicenseId
    ➥(String organizationId,String licenseId);
}

存储库接口LicenseRepository@Repository注解标记,这个注解告诉Spring应该将这个接口视为存储库并为它生成动态代理。Spring提供不同类型的数据访问存储库。我们选择使用Spring CrudRepository基类来扩展LicenseRepository类。CrudRepository基类包含基本的CRUD方法。除了从CrudRepository扩展的CRUD方法外,我们还添加了两个用于从许可表中检索数据的自定义查询方法。Spring Data框架将拆开这些方法的名称以构建访问底层数据的查询。

注意

Spring Data框架提供各种数据库平台上的抽象层,并不仅限于关系数据库。该框架还支持NoSQL数据库,如MongoDB和Cassandra。

与第2章中的许可证服务不同,我们现在已将许可证服务的业务逻辑和数据访问逻辑从LicenseController中分离出来,并划分在名为LicenseService的独立服务类中(如代码清单3-8所示)。

代码清单3-8 用于执行数据库命令的LicenseService

package com.thoughtmechanix.licenses.services;

import com.thoughtmechanix.licenses.config.ServiceConfig;
import com.thoughtmechanix.licenses.model.License;
import com.thoughtmechanix.licenses.repository.LicenseRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.UUID;

@Service
public class LicenseService {

    @Autowired
    private LicenseRepository licenseRepository;

    @Autowired
    ServiceConfig config;

    public License getLicense(String organizationId,String licenseId) {
        License license = licenseRepository.findByOrganizationIdAndLicenseId(
        ➥  organizationId, licenseId);
        return license.withComment(config.getExampleProperty());
    }

    public List<License> getLicensesByOrg(String organizationId){
        return licenseRepository.findByOrganizationId(organizationId);
    }

    public void saveLicense(License license){
        license.withId( UUID.randomUUID().toString());
        licenseRepository.save(license);
    } 
      /*为了简洁,省略了其余的代码*/
}

使用标准的Spring @Autowired注解将控制器、服务和存储库类连接到一起。

3.3.4 使用@Value 注解直接读取属性

在上一节的LicenseService类中,读者可能已经注意到,在getLicense()中使用了来自config.getExampleProperty()的值来设置license.withComment()的值。所指的代码如下:

public License getLicense(String organizationId,String licenseId) {
    License license = licenseRepository.findByOrganizationIdAndLicenseId(
organizationId, licenseId);
    return license.withComment(config.getExampleProperty());
}

如果查看licensing-service/src/main/java/com/thoughtmechanix/licenses/config/ServiceConfig.java,将看到使用@Value注解标注的属性。代码清单3-9展示了@Value注解的用法。

代码清单3-9 用于集中应用程序属性的**ServiceConfig**

package com.thoughtmechanix.licenses.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class ServiceConfig{
    @Value("${example.property}")
    private String exampleProperty;

    public String getExampleProperty(){
        return exampleProperty;
    }
}

虽然Spring Data“自动神奇地”将数据库的配置数据注入数据库连接对象中,但所有其他属性都必须使用@Value注解进行注入。在上述示例中,@Value注解从Spring Cloud配置服务器中提取example.property并将其注入ServiceConfig类的example.property属性中。

提示

虽然可以将配置的值直接注入各个类的属性中,但我发现将所有配置信息集中到一个配置类,然后将配置类注入需要它的地方是很有用的。

3.3.5 使用Spring Cloud配置服务器和Git

如前所述,使用文件系统作为Spring Cloud配置服务器的后端存储库,对基于云的应用程序来说是不切实际的,因为开发团队必须搭建和管理所有挂载在云配置服务器实例上的共享文件系统。

Spring Cloud配置服务器能够与不同的后端存储库集成,这些存储库可以用于托管应用程序配置属性。我成功地使用过Spring Cloud配置服务器与Git源代码控制存储库集成。

通过使用Git,我们可以获得将配置管理属性置于源代码管理下的所有好处,并提供一种简单的机制来将属性配置文件的部署集成到构建和部署管道中。

要使用Git,需要在配置服务的bootstrap.yml文件中使用代码清单3-10所示的配置替换文件系统的配置。

代码清单3-10 Spring Cloud配置的bootstrap.yml

server:
  port: 8888
spring:
  cloud:
    config:
      server:
        git:---  告诉Spring Cloud Config使用Git作为后端存储库
          uri: https://github.com/carnellj/config-repo/    ⇽---  告诉Spring Cloud Git服务器和Git存储库的URL
          searchPaths: licensingservice,organizationservice    ⇽---  告诉Spring Cloud Config在Git中查找配置文件的路径
          username: native-cloud-apps
          password: 0ffended

上述示例中的3个关键配置部分是spring.cloud.config.serverspring.cloud. config.server.git.urispring.cloud.config.server.git.searchPaths属性。spring.cloud.config.server属性告诉Spring Cloud配置服务器使用非基于文件系统的后端存储库。在上述例子中,将要连接到基于云的Git存储库GitHub。

spring.cloud.config.server.git.uri属性提供要连接的存储库URL。最后,spring. cloud.config.server.git.searchPaths属性告诉Spring Cloud Config服务器在云配置服务器启动时应该在Git存储库中搜索的相对路径。与配置的文件系统版本一样,spring.cloud. config.server.git.seachPaths属性中的值是以逗号分隔的由配置服务托管的服务列表。

3.3.6 使用Spring Cloud配置服务器刷新属性

开发团队想要使用Spring Cloud配置服务器时,遇到的第一个问题是,如何在属性变化时动态刷新应用程序。Spring Cloud配置服务器始终提供最新版本的属性,通过其底层存储库,对属性进行的更改将是最新的。

但是,Spring Boot应用程序只会在启动时读取它们的属性,因此Spring Cloud配置服务器中进行的属性更改不会被Spring Boot应用程序自动获取。Spring Boot Actuator提供了一个@RefreshScope注解,允许开发团队访问/refresh端点,这会强制Spring Boot应用程序重新读取应用程序配置。代码清单3-11展示了@RefreshScope注解的作用。

代码清单3-11 @RefreshScope注解

package com.thoughtmechanix.licenses;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.context.config.annotation.RefreshScope;

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

我们需要注意一些有关@RefreshScope注解的事情。首先,注解只会重新加载应用程序配置中的自定义Spring属性。Spring Data使用的数据库配置等不会被@RefreshScope注解重新加载。要执行刷新,可以访问http://:8080/refresh端点。

关于刷新微服务

将微服务与Spring Cloud配置服务一起使用时,在动态更改属性之前需要考虑的一件事是,可能会有同一服务的多个实例正在运行,需要使用新的应用程序配置刷新所有这些服务。有几种方法可以解决这个问题。

Spring Cloud配置服务确实提供了一种称为Spring Cloud Bus的“推送”机制,使Spring Cloud配置服务器能够向所有使用服务的客户端发布有更改发生的消息。Spring Cloud配置需要一个额外的中间件(RabbitMQ)运行。这是检测更改的非常有用的手段,但并不是所有的Spring Cloud配置后端都支持这种“推送”机制(也就是Consul服务器)。

在下一章中,我们将使用Spring Service Discovery和Eureka来注册所有服务实例。我用过的用于处理应用程序配置刷新事件的一种技术是,刷新Spring Cloud配置中的应用程序属性,然后编写一个简单的脚本来查询服务发现引擎以查找服务的所有实例,并直接调用/refresh端点。

最后一种方法是重新启动所有服务器或容器来接收新的属性。这项工作很简单,特别是在Docker等容器服务中运行服务时。重新启动Docker容器差不多需要几秒,然后将强制重新读取应用程序配置。

记住,基于云的服务器是短暂的。不要害怕使用新配置启动服务的新实例,直接使用新服务,然后拆除旧的服务。

3.4 保护敏感的配置信息

在默认情况下,Spring Cloud配置服务器在应用程序配置文件中以纯文本格式存储所有属性,包括像数据库凭据这样的敏感信息。

将敏感凭据作为纯文本保存在源代码存储库中是一种非常糟糕的做法。遗憾的是,它发生的频率比我们想象的要高得多。Spring Cloud Config可以让我们轻松加密敏感属性。Spring Cloud Config支持使用对称加密(共享密钥)和非对称加密(公钥/私钥)。

我们将看看如何搭建Spring Cloud配置服务器以使用对称密钥的加密。要做到这一点,需要:

1)下载并安装加密所需的Oracle JCE jar;

(2)创建加密密钥;

(3)加密和解密属性;

(4)配置微服务以在客户端使用加密。

3.4.1 下载并安装加密所需的Oracle JCE jar

首先,需要下载并安装Oracle的不限长度的Java加密扩展(Unlimited Strength Java Cryptography Extension,JCE)。它无法通过Maven下载,必须从Oracle公司下载[[1]](javascript:void(0))。下载包含JCE jar的zip文件后,必须执行以下操作。

(1)切换到$JAVA_HOME/jre/lib/security文件夹。

(2)将$JAVA_HOME/jre/lib/security目录中的local_policy.jar和US_export_policy.jar文件备份到其他位置。

(3)解压从Oracle下载的JCE zip文件。

(4)将local_policy.jar和US_export_policy.jar复制到$JAVA_HOME/jre/lib/security目录中。

(5)配置Spring Cloud Config以使用加密。

自动化安装Oracle JCE文件的过程

我已经完成了在笔记本电脑上安装JCE所需的手动步骤。因为我们使用Docker将所有的服务构建为Docker容器,所以我已经在Spring Cloud Config Docker容器中编写了这些JAR文件的下载和安装的脚本。下面的OS X shell脚本代码段展示了如何使用curl命令行工具进行自动化操作:

cd /tmp/
curl –k-LO "http://download.oracle.com/otn-pub/java/jce/8/jce_policy-8.zip"
     -H 'Cookie: oraclelicense=accept-securebackup-cookie' && unzip
     jce_policy-8.zip
rm jce_policy-8.zip
yes |cp -v /tmp/UnlimitedJCEPolicyJDK8/­*.jar /usr/lib/jvm/java-1.8-openjdk/jre/
     lib/security/

我不会去讲所有的细节,但基本上我使用CURL下载了JCE zip文件(注意通过curl命令中的-H属性传递的Cookie头参数),然后解压文件并将其复制到Docker容器中的/usr/lib/jvm/java-1.8- openjdk/jre/lib/security目录。

如果读者查看本章源代码中的src/main/docker/Dockerfile文件,就可以看到该脚本的示例。

3.4.2 创建加密密钥

一旦JAR文件就位,就需要设置一个对称加密密钥。对称加密密钥只不过是加密器用来加密值和解密器用来解密值的共享密钥。使用Spring Cloud配置服务器,对称加密密钥是通过操作系统环境变量ENCRYPT_KEY传递给服务的字符串。在本书中,需要始终将ENCRYPT_KEY环境变量设置为:

export ENCRYPT_KEY=IMSYMMETRIC

关于对称密钥,要注意以下两点。

(1)对称密钥的长度应该是12个或更多个字符,最好是一个随机的字符集。

(2)不要丢失对称密钥。一旦使用加密密钥加密某些东西,如果没有对称密钥就无法解密。

管理加密密钥

为了撰写本书,我做了两件在生产部署中通常不会推荐的事情。

  • 我将加密密钥设置为一句话。因为我想保持密钥简单,以便我能记住它,并且它能很好地进行阅读。在真实的部署中,我会为部署的每个环境使用单独的加密密钥,并使用随机字符作为我的密钥。
  • 我直接在本书中使用的Docker文件中硬编码了ENCRYPT_KEY环境变量。我这样做是为了让读者可以下载文件并启动它们而无需设置环境变量。在真实的运行时环境中,我将引用ENCRYPT_KEY作为Docker文件中的一个操作系统环境变量。注意这一点,并且不要在Dockerfile内硬编码加密密钥。记住,Dockerfile应该处于源代码管理下。

3.4.3 加密和解密属性

现在,可以开始加密在Spring Cloud Config中使用的属性了。我们将加密用于访问EagleEye数据的许可证服务Postgres数据库密码。要加密的属性spring.datasource.password当前设置的纯文本值为p0stgr@s

在启动Spring Cloud Config实例时,Spring Cloud Config将检测到环境变量ENCRYPT_KEY已设置,并自动将两个新端点(/encrypt/decrypt)添加到Spring Cloud Config服务。我们将使用/encrypt端点加密p0stgr@s值。

图3-8展示了如何使用/encrypt端点和POSTMAN加密p0stgr@s的值。请注意,无论何时调用/encrypt/decrypt端点,都需要确保对这些端点进行POST请求。

image-20210902102106258

图3-8 使用/encrypt端点可以加密值

如果要解密这个值,可以使用/decrypt端点,在调用中传递已加密的字符串。

现在可以使用以下语法将已加密的属性添加到GitHub或基于文件系统的许可证服务的配置文件中:

spring.datasource.password:"{cipher}
➥  858201e10fe3c9513e1d28b33ff417a66e8c8411dcff3077c53cf53d8a1be360"

Spring Cloud配置服务器要求所有已加密的属性前面加上{cipher}{cipher}告诉Spring Cloud配置服务器它正在处理已加密的值。启动Spring Cloud配置服务器,并使用GET方法访问http://localhost:8888/licensingservice/default端点。

图3-9展示了这次调用的结果。

image-20210902102205166

图3-9 虽然在属性文件中,spring.datasource.password已经被加密,然而当许可证服务的配置被检索时,它将被解密。这仍然是有问题的

我们通过对属性进行加密来让spring.datasource.password变得更安全,但仍然存在一个问题。在访问http://localhost:8888/licensingservice/default端点时,数据库密码被以纯文本形式公开了。

在默认情况下,Spring Cloud Config将在服务器上解密所有属性,并将未加密的纯文本作为结果传回给请求属性的应用程序。但是,开发人员可以告诉Spring Cloud Config不要在服务器上进行解密,并让应用程序负责检索配置数据以解密已加密的属性。

3.4.4 配置微服务以在客户端使用加密

要让客户端对属性进行解密,需要做以下3件事情。

(1)配置Spring Cloud Config不要在服务器端解密属性。

(2)在许可证服务器上设置对称密钥。

(3)将spring-security-rsa JAR添加到许可证服务的pom.xml文件中。

首先需要做的是在Spring Cloud Config中禁用服务器端的属性解密。这可以通过设置Spring Cloud Config的src/main/resources/application.yml文件中的spring.cloud.config.server.encrypt.enabled属性为false来完成。这就是在Spring Cloud Config服务器上需要做的所有工作。

因为许可证服务现在负责解密已加密的属性,所以需要先在许可证服务上设置对称密钥,方法是确保ENCRYPT_KEY环境变量与Spring Cloud Config服务器使用的对称密钥相同(如IMSYMMETRIC)

接下来,需要在许可证服务中包含spring-security-rsa JAR依赖项:


  org.springframework.security
  spring-security-rsa

这些JAR文件包含解密从Spring Cloud Config检索的已加密的属性所需的Spring代码。有了这些更改,就可以启动Spring Cloud Config和许可证服务了。如果读者访问http://localhost:8888/licensingservice/default端点,就会发现spring.datasource.password是以加密形式返回的。图3-10展示了这一调用的输出结果。

image-20210902102526293

图3-10 启用客户端解密后,敏感属性不再以未加密文本的形式从Spring Cloud Config REST调用中返回。相反,在从Spring Cloud Config加载属性时,该属性将由调用服务解密

3.5 最后的想法

应用程序配置管理可能看起来像一个普通的主题,但它在基于云的环境中至关重要。正如我们将在后面几章中更详细地讨论的,非常重要的一点在于应用程序以及它们运行的服务器是不可变的,并且不会手动更改在不同环境间进行部署提升的服务器。这与传统部署模型的情况是不一样的,在传统部署模型中,开发人员会将应用程序制品(如JAR或WAR文件)连同它的属性文件一起部署到一个“固定的”环境中。

使用基于云的模型,应用程序配置数据应该与应用程序完全分离,并在运行时注入相应的配置数据,以便在所有环境中一致地提升相同的服务器和应用程序制品。

3.6 小结

  • Spring Cloud配置服务器允许使用环境特定值创建应用程序属性。
  • Spring使用Spring profile来启动服务,以确定要从Spring Cloud Config服务检索哪些环境属性。
  • Spring Cloud配置服务可以使用基于文件或基于Git的应用程序配置存储库来存储应用程序属性。
  • Spring Cloud配置服务允许使用对称加密和非对称加密对敏感属性文件进行加密。

[[1]](javascript:void(0)) 在Google快速搜索Java Cryptography Extensions应该能找到正确的URL。


源码地址

https://github.com/WaterMoonMirror/spmia-chapter3

式返回的。图3-10展示了这一调用的输出结果。

[外链图片转存中…(img-2E36ICPa-1631243645766)]

图3-10 启用客户端解密后,敏感属性不再以未加密文本的形式从Spring Cloud Config REST调用中返回。相反,在从Spring Cloud Config加载属性时,该属性将由调用服务解密

3.5 最后的想法

应用程序配置管理可能看起来像一个普通的主题,但它在基于云的环境中至关重要。正如我们将在后面几章中更详细地讨论的,非常重要的一点在于应用程序以及它们运行的服务器是不可变的,并且不会手动更改在不同环境间进行部署提升的服务器。这与传统部署模型的情况是不一样的,在传统部署模型中,开发人员会将应用程序制品(如JAR或WAR文件)连同它的属性文件一起部署到一个“固定的”环境中。

使用基于云的模型,应用程序配置数据应该与应用程序完全分离,并在运行时注入相应的配置数据,以便在所有环境中一致地提升相同的服务器和应用程序制品。

3.6 小结

  • Spring Cloud配置服务器允许使用环境特定值创建应用程序属性。
  • Spring使用Spring profile来启动服务,以确定要从Spring Cloud Config服务检索哪些环境属性。
  • Spring Cloud配置服务可以使用基于文件或基于Git的应用程序配置存储库来存储应用程序属性。
  • Spring Cloud配置服务允许使用对称加密和非对称加密对敏感属性文件进行加密。

[[1]](javascript:void(0)) 在Google快速搜索Java Cryptography Extensions应该能找到正确的URL。


源码地址

https://github.com/WaterMoonMirror/spmia-chapter3

你可能感兴趣的:(微服务,spring,java,数据库)