# Spring 保险库-参考文献

# 序言

Spring Vault 项目将核心 Spring 概念应用于使用 HashiCorpVault 的解决方案的开发。我们提供了一个“模板”作为存储和查询文档的高级抽象。你将注意到与 Spring 框架中的 REST 支持的相似之处。

这份文件是 Spring Vault 的参考指南。它解释了 Vault 的概念、语义和语法。

参考文档的这一部分解释了 Spring Vault 提供的核心功能。

[保险库支持](#vault.core)介绍了 Vault 模块功能集。

## 1.文件结构

这一部分提供了 Spring 和 Vault 的基本介绍。它包含有关后续开发和如何获得支持的详细信息。

文档的其余部分引用了 Spring Vault 特性，并假定用户熟悉[HashiCorp 保险库](https://www.vaultproject.io)以及 Spring 概念。

## 2.知道 Spring

Spring Vault 使用 Spring Framework 的[core](https://docs.spring.io/spring/docs/5.3.4/spring-framework-reference/core.html)功能，例如[IoC](https://docs.spring.io/spring/docs/5.3.4/spring-framework-reference//core.html)容器。虽然了解 Spring API 并不重要，但了解它们背后的概念是重要的。至少，对于你选择使用的任何 IOC 容器，IOC 背后的思想应该是熟悉的。

Vault 支持的核心功能可以直接使用，而不需要调用 Spring 容器的 IoC 服务。这很像`RestTemplate`，它可以在没有 Spring 容器的任何其他服务的情况下“独立”使用。为了利用 Spring Vault 文档的所有特性，例如会话支持，你将需要使用 Spring 配置库的某些部分。

要了解有关 Spring 的更多信息，你可以参考详细解释 Spring 框架的全面（有时是解除武装）文档。有很多关于这个问题的文章、博客条目和书籍--看看 Spring 框架[home page ](https://spring.io/docs)，了解更多信息。

## 3.知道保险库

安全性和处理秘密是每个处理数据库、用户凭据或 API 密钥的开发人员关心的问题。Vault 通过提供与访问控制、撤销、密钥滚动和审计相结合的安全存储来介入。简而言之：Vault 是一种安全访问和存储秘密的服务。秘密是你想要严格控制访问权限的任何东西，例如 API 密钥、密码、证书等等。

学习跳马的起点是[WWW，VaultProject.io。](https://www.vaultproject.io)。以下是一些有用的资源：

* 该手册介绍了 Vault，并包含入门指南、参考文档和教程的链接。

* Online Shell 结合在线教程提供了一种方便的方式来与 Vault 实例交互。

* [HashiCorp 保险库介绍](https://www.vaultproject.io/intro/index.html)

* [HashiCorp 保险库文档](https://www.vaultproject.io/docs/index.html)

Spring Vault 提供了用于访问、存储和撤销秘密的客户端支持。有了[HashiCorp 的保险库](https://www.vaultproject.io)，你就有了一个中心位置来管理跨所有环境的应用程序的外部秘密数据。Vault 可以管理静态和动态秘密，例如应用程序数据、远程应用程序/资源的用户名/密码，并为外部服务（例如 MySQL、PostgreSQL、 Apache Cassandra、Consul、AWS 等）提供凭据。

## 4.所需经费

Spring Vault2.x 二进制文件要求 JDK 级别为 8.0 及以上，并且[Spring Framework](https://spring.io/docs)5.3.4 及以上。

就保险库而言，[Vault](https://www.vaultproject.io/)至少为 0.6.

## 5.额外的帮助资源

学习一个新的框架并不总是直截了当的。在这一部分中，我们试图提供一种我们认为易于遵循的指南，用于从 Spring Vault 模块开始。然而，如果你遇到问题或你只是在寻求建议，请使用以下链接之一：

### 5.1.支持

有几个可用的支持选项：

#### 5.1.1.社区论坛

在[StackOverflow](https://stackoverflow.com/questions/tagged/spring-vault)上发布有关 Spring Vault 的问题，以共享信息并相互帮助。请注意，需要注册**只有**才能发布。

#### 5.1.2.专业支持

Spring Vault 和 Spring 背后的公司[Pivotal Software，Inc.](https://pivotal.io/)提供专业的、源代码支持，并保证响应时间。

### 5.2.后续发展

有关 Spring Vault 源代码库、夜间构建和快照工件的信息，请参见[Spring Vault homepage](https://projects.spring.io/spring-vault/)。通过在[StackOverflow](https://stackoverflow.com/questions/tagged/spring-vault)上通过社区与开发人员进行交互，你可以帮助使 Spring Vault 最好地满足 Spring 社区的需求。如果你遇到错误或希望提出改进建议，请在 Spring vault 问题[tracker](https://github.com/spring-projects/spring-vault/issues)上创建一个票证。要了解 Spring 生态系统中的最新消息和公告，请订阅 Spring 社区[Portal](https://spring.io)。最后，你可以关注 Spring [blog ](https://spring.io/blog)或 Twitter 上的项目团队（[SpringCentral](https://twitter.com/springcentral)）。

## 6.新的和值得注意的

### 6.1.最新更新在 Spring Vault2.3 中

* 支持用于密钥库和信任库使用的 PEM 编码证书。

* `ReactiveVaultEndpointProvider`用于`VaultEndpoint`的非阻塞查找。

* `VaultKeyValueMetadataOperations`用于键值元数据交互。

* 支持`transform`后端（Enterprise 功能）。

* [如何使用保险库秘密后端](#vault.core.secret-engines)的文档。

* 每次登录尝试都会重新加载 Kubernetes 和 PCF 身份验证的登录凭据。

* `SecretLeaseContainer`在成功的秘密旋转时发布`SecretLeaseRotatedEvent`而不是`SecretLeaseExpiredEvent`和`SecretLeaseCreatedEvent`。

* `AbstractVaultConfiguration.threadPoolTaskScheduler()` Bean 类型更改为`TaskSchedulerWrapper`而不是`ThreadPoolTaskScheduler`。

### 6.2.最新更新在 Spring Vault2.2 中

* 通过`@VaultPropertySource`支持键值 v2（版本控制的后端）秘密。

* spel 支持`@Secret`。

* 添加对 Jetty 的支持作为反应性 HttpClient。

* `LifecycleAwareSessionManager`和`ReactiveLifecycleAwareSessionManager`现在发射`AuthenticationEvent`s。

* [PCF 认证](#vault.authentication.pcf).

* 反对`AppIdAuthentication`。使用`AppRoleAuthentication`，而不是按照 HashiCorp 保险库的建议。

* `CubbyholeAuthentication`和 wrapped`AppRoleAuthentication`现在默认使用`sys/wrapping/unwrap`端点。

* Kotlin 协程支持`ReactiveVaultOperations`。

### 6.3.最新更新在 Spring Vault2.1 中

* [GCP 计算](#vault.authentication.gcpgce)，[GCP IAM](#vault.authentication.gcpiam)，以及[Azure](#vault.authentication.azuremsi)身份验证。

* 模板 API 支持版本控制和非版本控制的键/值后端和 Vault 包装操作。

* 在反应式认证中支持完全拉动模式.

* 改进了保险库登录失败的异常层次结构。

### 6.4.最新更新在 Spring Vault2.0 中

* 身份验证步骤 dsl 到[组合认证流](#vault.authentication.steps)。

* [反应式保险库客户端](#vault.core.reactive.template)via`ReactiveVaultOperations`。

* 基于 Spring 数据键值的[保险库存储库支持](#vault.repositories)。

* 支持传输批加密和解密.

* 存储为 JSON 的策略的策略管理。

* 支持 CSR 签名、证书撤销和 CRL 检索。

* [Kubernetes 认证](#vault.authentication.kubernetes).

* roleid/secretid 展开[Approle 身份验证](#vault.authentication.approle)。

* [Spring Security integration](#vault.misc.spring-security)基于传输后端的`BytesKeyGenerator`和`BytesEncryptor`。

### 6.5.最新更新在 Spring Vault1.1.0 中

* [AWS IAM 身份验证](#vault.authentication.awsiam).

* 为传输密钥配置加密/解密版本。

* [Approle 身份验证](#vault.authentication.approle)的拉动模式。

* 支持传输批加密和解密.

* 基于 TTL 的通用秘密旋转。

### 6.6.最新更新在 Spring Vault1.0 中

* 最初的保险库支持。

# 参考文献

## 7.保险库支持

Vault 支持包含一系列广泛的功能，这些功能概述如下。

* Spring 使用基于 Java 的配置支持 @Configuration Classes

* `VaultTemplate`帮助类，用于提高执行公共保险库操作的生产率。包括保险库响应和 POJO 之间的集成对象映射。

对于大多数任务，你会发现自己正在使用`VaultTemplate`，它利用了丰富的通信功能。`VaultTemplate`是查找访问功能（例如从 Vault 读取数据或发出管理命令）的位置。`VaultTemplate`还提供了回调方法，这样你就可以轻松地获得低级 API 工件，例如`RestTemplate`，从而直接与 Vault 通信。

### 7.1.依赖关系

查找 Spring Vault 依赖关系的兼容版本的最简单的方法是依赖于我们提供的 Spring Vault BOM 以及定义的兼容版本。在 Maven 项目中，你将在你的`pom.xml`的`<dependencyManagement />`部分中声明此依赖项：

例 1.使用 Spring 保险库 BOM

```
<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework.vault</groupId>
      <artifactId>spring-vault-dependencies</artifactId>
      <version>2.3.1</version>
      <scope>import</scope>
      <type>pom</type>
    </dependency>
  </dependencies>
</dependencyManagement>
```

目前的版本是`2.3.1`。版本名遵循以下模式：`${version}`用于 GA 和服务版本，`${version}-${release}`用于快照和里程碑。`release`可以是下列情况之一：

* `SNAPSHOT`-当前快照

* `M1`，`M2`等-里程碑

* `RC1`，`RC2`等-释放候选项

例 2.声明对 Spring Vault 的依赖关系

```
<dependencies>
    <dependency>
        <groupId>org.springframework.vault</groupId>
        <artifactId>spring-vault-core</artifactId>
    </dependency>
</dependencies>
```

### 7.2. Spring 框架

Spring Vault 的当前版本需要版本 5.3.4 或更好的 Spring 框架。这些模块还可以与该小版本的旧 Bugfix 版本一起工作。但是，强烈建议你在这一代中使用最新的版本。

## 8.开始

Spring Vault 支持需要 Vault0.6 或更高版本和 Java SE6 或更高版本。引导设置工作环境的一种简单方法是在[STS](https://spring.io/tools/sts)中创建一个基于 Spring 的项目。

首先，你需要设置一个运行的保险库服务器。有关如何启动 Vault 实例的说明，请参阅[Vault](https://www.vaultproject.io/intro/)。

要在 STS 中创建 Spring 项目，请转到文件 New Spring Template Project Simple Spring Utility Project，在提示时按 Yes。然后输入一个项目和一个包名，如`org.spring.vault.example`。

然后将以下内容添加到`pom.xml`依赖关系部分。

例 3.添加 Spring 保险库依赖项

```
<dependencies>

  <!-- other dependency elements omitted -->

  <dependency>
    <groupId>org.springframework.vault</groupId>
    <artifactId>spring-vault-core</artifactId>
    <version>2.3.1</version>
  </dependency>

</dependencies>
```

如果你正在使用一个里程碑或候选版本，那么你还需要将 Spring 里程碑存储库的位置添加到你的 Maven `pom.xml`中，该位置与你的`<dependencies/>`元素处于同一级别。

```
<repositories>
  <repository>
    <id>spring-milestone</id>
    <name>Spring Maven MILESTONE Repository</name>
    <url>https://repo.spring.io/libs-milestone</url>
  </repository>
</repositories>
```

存储库也是[在这里可浏览](https://repo.spring.io/milestone/org/springframework/vault/)。

如果正在使用快照，还需要将 Spring 快照库的位置添加到你的 Maven `pom.xml`中，该位置与你的`<dependencies/>`元素处于同一级别。

```
<repositories>
  <repository>
    <id>spring-snapshot</id>
    <name>Spring Maven SNAPSHOT Repository</name>
    <url>https://repo.spring.io/libs-snapshot</url>
  </repository>
</repositories>
```

存储库也是[在这里可浏览](https://repo.spring.io/snapshot/org/springframework/vault/)。

创建一个简单的`Secrets`类以持久存在：

例 4.映射数据对象

```
package org.spring.vault.example;

public class Secrets {

    String username;
    String password;

    public String getUsername() {
        return username;
    }

    public String getPassword() {
        return password;
    }
}
```

以及要运行的主应用程序

例 5.使用 Spring Vault 的示例应用程序

```
package org.springframework.vault.example;

import org.springframework.vault.authentication.TokenAuthentication;
import org.springframework.vault.client.VaultEndpoint;
import org.springframework.vault.core.VaultTemplate;
import org.springframework.vault.support.VaultResponseSupport;

public class VaultApp {

    public static void main(String[] args) {

        VaultTemplate vaultTemplate = new VaultTemplate(new VaultEndpoint(),
                new TokenAuthentication("00000000-0000-0000-0000-000000000000"));

        Secrets secrets = new Secrets();
        secrets.username = "hello";
        secrets.password = "world";

        vaultTemplate.write("secret/myapp", secrets);

        VaultResponseSupport<Secrets> response = vaultTemplate.read("secret/myapp", Secrets.class);
        System.out.println(response.getData().getUsername());

        vaultTemplate.delete("secret/myapp");
    }
}
```

即使在这个简单的例子中，也没有什么值得注意的地方。

* 可以使用`org.springframework.vault.client.VaultEndpoint`对象和`ClientAuthentication`实例化 Spring Vault 的中心类[`VaultTemplate`]（#vault.core.template）。你不需要旋转 Spring 上下文来使用 Spring vault。

* Vault 将被配置为使用`00000000-0000-0000-0000-000000000000`的根令牌来运行此应用程序。

* 该映射器针对标准的 POJO 对象工作，而不需要任何额外的元数据（尽管你可以选择提供该信息）。

* 映射约定可以使用字段访问。注意`Secrets`类只有 getter。

* 如果构造函数参数名称与存储文档的字段名称匹配，则将使用它们实例化对象。

## 9.VaultTemplate 简介

类`VaultTemplate`位于包`org.springframework.vault.core`中，是 Spring 的 Vault 支持的中心类，提供了与 Vault 交互的丰富功能集。该模板提供了在 Vault 中读、写和删除数据的方便操作，并提供了域对象和 Vault 数据之间的映射。

|   |一旦配置完成，`VaultTemplate`是线程安全的，并且可以在<br/>多个实例中重用。|
|---|------------------------------------------------------------------------------------------------|

Vault 文档和域类之间的映射是通过委托给`RestTemplate`来完成的。 Spring Web 支持提供了映射基础设施。

`VaultTemplate`类实现了接口`VaultOperations`。在尽可能多的情况下，`VaultOperations`上的方法是以 Vault API 上可用的方法命名的，以使熟悉 API 和 CLI 的现有 Vault 开发人员熟悉该 API。例如，你将找到诸如“write”、“delete”、“read”和“revoke”等方法。设计目标是使 Vault API 的使用和`VaultOperations`之间的转换变得尽可能容易。这两个 API 之间的一个主要区别是，`VaultOperations`可以传递域对象，而不是 JSON 键-值对。

|   |引用`VaultTemplate`实例<br/>上的操作的首选方法是通过其接口`VaultOperations`。|
|---|---------------------------------------------------------------------------------------------------------------------|

虽然在`VaultTemplate`上有许多方便的方法可以帮助你轻松地执行常见任务，如果你需要直接访问 Vault API 以访问`VaultTemplate`未显式暴露的功能，则可以使用几种执行回调方法中的一种来访问底层 API。Execute 回调将为你提供对`RestOperations`对象的引用。有关更多信息，请参见[执行回调](#vault.core.executioncallback)一节。

现在，让我们来看看如何在 Spring 容器的上下文中使用 Vault 的示例。

### 9.1.注册和配置 Spring Vault bean

使用 Spring Vault 不需要 Spring 上下文。但是，在托管上下文中注册的`VaultTemplate`和`SessionManager`实例将参与由 Spring IOC 容器提供的[生命周期事件](https://docs.spring.io/spring/docs/5.3.4/spring-framework-reference/core.html#beans-factory-nature)。这对于在应用程序关闭时处理活动的 Vault 会话非常有用。你还可以在应用程序中重用相同的`VaultTemplate`实例。

Spring Vault 附带了一个支持配置类，该配置类提供了 Bean 用于在 Spring 上下文中使用的定义。应用程序配置类通常从`AbstractVaultConfiguration`扩展，并且需要提供环境特定的附加细节。

从`AbstractVaultConfiguration`扩展需要实现` VaultEndpoint vaultEndpoint()`和`ClientAuthentication clientAuthentication()`方法。

例 6.使用基于 Java 的 Bean 元数据注册 Spring Vault 对象

```
@Configuration
public class AppConfig extends AbstractVaultConfiguration {

    /**
     * Specify an endpoint for connecting to Vault.
     */
    @Override
    public VaultEndpoint vaultEndpoint() {
        return new VaultEndpoint();                            (1)
    }

    /**
     * Configure a client authentication.
     * Please consider a more secure authentication method
     * for production use.
     */
    @Override
    public ClientAuthentication clientAuthentication() {
        return new TokenAuthentication("…");                   (2)
    }
}
```

|**1**|创建一个新的`VaultEndpoint`，默认情况下指向`https://localhost:8200`。|
|-----|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|**2**|此示例使用`TokenAuthentication`快速启动。<br/>有关支持的身份验证方法的详细信息，请参见[认证方法](#vault.core.authentication)。|

例 7.使用注入的财产登记 Spring 保险库

```
@Configuration
public class AppConfig extends AbstractVaultConfiguration {

    @Value("${vault.uri}")
    URI vaultUri;

    /**
     * Specify an endpoint that was injected as URI.
     */
    @Override
    public VaultEndpoint vaultEndpoint() {
        return VaultEndpoint.from(vaultUri);                          (1)
    }

    /**
     * Configure a Client Certificate authentication.
     * {@link RestOperations} can be obtained from {@link #restOperations()}.
     */
    @Override
    public ClientAuthentication clientAuthentication() {
        return new ClientCertificateAuthentication(restOperations()); (2)
    }
}
```

|**1**|`VaultEndpoint`可以使用各种工厂方法构建，例如`from(URI uri)`或`VaultEndpoint.create(String host, int port)`。|
|-----|------------------------------------------------------------------------------------------------------------------------------------------|
|**2**|`ClientAuthentication`方法的依赖关系可以从`AbstractVaultConfiguration`获得，也可以由配置提供。|

|   |在某些情况下，创建自定义配置类可能很麻烦。<br/>看看`EnvironmentVaultConfiguration`，它允许使用现有属性源的<br/>属性和 Spring 的`Environment`进行配置。在[using`EnvironmentVaultConfiguration`]（#vault.core.environment-vault-configuration）中阅读更多<br/>。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

### 9.2.会话管理

Spring Vault 需要`ClientAuthentication`才能登录和访问 Vault。有关身份验证的详细信息，请参见[认证方法](#vault.core.authentication)。Vault 登录不应该发生在每个经过身份验证的 Vault 交互上，而是必须在整个会话中重用。该方面由`SessionManager`实现来处理。a`SessionManager`决定它获得令牌的频率，关于撤销和更新。 Spring Vault 有两种实现方式：

* `SimpleSessionManager`:只需从提供的`ClientAuthentication`获取令牌，而无需刷新和撤销

* `LifecycleAwareSessionManager`:如果令牌是可更新的，则此`SessionManager`调度令牌更新，并在处置时撤销登录令牌。更新计划使用`AsyncTaskExecutor`。如果使用`AbstractVaultConfiguration`，则默认配置`LifecycleAwareSessionManager`。

### 9.3.使用`EnvironmentVaultConfiguration`

Spring Vault 包括从 Spring 的`Environment`中配置 Vault 客户端的`EnvironmentVaultConfiguration`和一组预定义的属性键。`EnvironmentVaultConfiguration`支持经常应用的配置。从最合适的配置类派生支持其他配置。将`EnvironmentVaultConfiguration`与`@Import(EnvironmentVaultConfiguration.class)`一起包含到现有的基于 Java 的配置类中，并通过 Spring 的`PropertySource`s 中的任何一个提供配置属性。

例 8.对属性文件使用 EnvironmentVaultConfiguration

基于 Java 的配置类

```
@PropertySource("vault.properties")
@Import(EnvironmentVaultConfiguration.class)
public class MyConfiguration{
}
```

Vault.Properties

```
vault.uri=https://localhost:8200
vault.token=00000000-0000-0000-0000-000000000000
```

**属性键**

* Vault URI:`vault.uri`

* SSL 配置

  * 密钥存储库资源：`vault.ssl.key-store`（可选）

  * 密钥存储库密码：`vault.ssl.key-store-password`（可选）

  * 密钥存储库类型：`vault.ssl.key-store-type`（可选的，通常`jks`，还支持`pem`）

  * 信任存储资源：`vault.ssl.trust-store`（可选）

  * 信任存储库密码：`vault.ssl.trust-store-password`（可选）

  * 信任存储类型：`vault.ssl.trust-store-type`（可选的，通常`jks`，也支持`pem`）

* 认证方法：`vault.authentication`（默认为`TOKEN`，支持的认证方法有：`TOKEN`，`APPID`，`APPROLE`，`AWS_EC2`，`AZURE`，`CUBBYHOLE`，`KUBERNETES`）

**特定于身份验证的属性密钥**

**[令牌认证](#vault.authentication.token)**

* 金库令牌：`vault.token`

**[APPID 身份验证](#vault.authentication.appid)**

* APPID 路径：`vault.app-id.app-id-path`（默认为`app-id`）

* appid:`vault.app-id.app-id`

* userid:`vault.app-id.user-id`。`MAC_ADDRESS`和`IP_ADDRESS`使用`MacAddressUserId`，相应的`IpAddressUserId`用户 ID 机制。任何其他值都与`StaticUserId`一起使用。

**[Approle 身份验证](#vault.authentication.approle)**

* 路径：`vault.app-role.app-role-path`（默认为`approle`）

* ROLEID:`vault.app-role.role-id`

* secretID:`vault.app-role.secret-id`（可选）

**[AWS-EC2 身份验证](#vault.authentication.awsec2)**

* AWS EC2 路径：`vault.aws-ec2.aws-ec2-path`（默认为`aws-ec2`）

* 角色：`vault.aws-ec2.role`

* ROLEID:`vault.aws-ec2.role-id`（** 不推荐：** 用`vault.aws-ec2.role`代替）

* 身份证件网址：`vault.aws-ec2.identity-document`（默认为`[http://169.254.169.254/latest/dynamic/instance-identity/pkcs7](http://169.254.169.254/latest/dynamic/instance-identity/pkcs7)`）

**[Azure（MSI）认证](#vault.authentication.azuremsi)**

* Azure MSI 路径：`vault.azure-msi.azure-path`（默认为`azure`）

* 角色：`vault.azure-msi.role`

* 元数据服务 URL:`vault.azure-msi.metadata-service`（默认为`[http://169.254.169.254/metadata/instance?api-version=2017-08-01](http://169.254.169.254/metadata/instance?api-version=2017-08-01)`）

* Identity TokenService URL:`vault.azure-msi.identity-token-service`（默认为`[http://169.254.169.254/metadata/identity/oauth2/token?resource=https://vault.hashicorp.com&api-version=2018-02-01](http://169.254.169.254/metadata/identity/oauth2/token?resource=https://vault.hashicorp.com&api-version=2018-02-01)`）

**[TLS 证书认证](#vault.authentication.clientcert)**

没有配置选项。

**[空穴身份验证](#vault.authentication.cubbyhole)**

* 初始保险库令牌：`vault.token`

**[Kubernetes 认证](#vault.authentication.kubernetes)**

* Kubernetes 路径：`vault.kubernetes.kubernetes-path`（默认为`kubernetes`）

* 角色：`vault.kubernetes.role`

* 服务帐户令牌文件的路径：`vault.kubernetes.service-account-token-file`（默认为`/var/run/secrets/kubernetes.io/serviceaccount/token`）

### 9.4.执行回调

所有 Spring 模板类的一个常见设计特征是，所有功能都被路由到一个模板执行回调方法。这有助于确保执行异常和可能需要的任何资源管理的一致性。虽然在 JDBC 和 JMS 的情况下，这比 Vault 的需要大得多，但它仍然为访问和日志记录的发生提供了一个单一的位置。因此，使用 Execute Callback 是访问 Vault API 的首选方式，以执行我们尚未作为`VaultTemplate`上的方法公开的不常见操作。

下面是执行回调方法的列表。

* `<T> T`**DowithVault**`(RestOperationsCallback<T> callback)`执行给定的`RestOperationsCallback`，允许使用`RestOperations`与 Vault 进行交互，而不需要会话。

* `<T> T`**首次会议**`(RestOperationsCallback<T> callback)`执行给定的`RestOperationsCallback`，允许在经过身份验证的保险库中进行交互会话。

下面是一个使用`ClientCallback`初始化 Vault 的示例：

```
vaultOperations.doWithVault(new RestOperationsCallback<VaultInitializationResponse>() {

  @Override
  public VaultInitializationResponse doWithRestOperations(RestOperations restOperations) {

    ResponseEntity<VaultInitializationResponse> exchange = restOperations
                       .exchange("/sys/init", HttpMethod.PUT,
                                 new HttpEntity<Object>(request),
                                 VaultInitializationResponse.class);

    return exchange.getBody();
    }
});
```

## 10.支持 Vault 的秘密引擎

Spring Vault 船有几个扩展，以支持 Vault 的各种秘密引擎。

具体地说， Spring 带有扩展的保险库船舶用于：

* [键值版本 1（“无版本的秘密”）](#vault.core.backends.kv1)

* [键值版本 2（“版本管理的秘密”）](#vault.core.backends.kv2)

* [PKI（公开密钥基础设施）](#vault.core.backends.pki)

* [令牌认证后端](#vault.core.backends.token)

* 转换（Enterprise 特性）

* [传输后端](#vault.core.backends.transit)

* 系统后端

你可以通过`VaultTemplate`上的方法直接使用所有其他后端（`VaultTemplate.read(…)`，`VaultTemplate.write(…)`）。

### 10.1.键值版本 1（“无版本的秘密”）

`kv`秘密引擎用于在 Vault 配置的物理存储中存储任意秘密。

当以一种非版本管理的方式运行`kv`秘密引擎时，只保留密钥的最近写入的值。无版本 KV 的好处是减少了每个键的存储大小，因为不会存储额外的元数据或历史记录。此外，以这种方式配置到后端的请求性能更好，因为对于任何给定的请求，存储调用更少，也没有锁定。

Spring Vault 附带一个专用的键值 API，以封装各个键值 API 实现之间的差异。`VaultKeyValueOperations`遵循 Vault CLI 的设计。这是 Vault 提供诸如`vault kv get`、`vault kv put`等命令的主要命令行工具。

通过指定版本和挂载路径，你可以将此 API 用于两个键值引擎版本。下面的示例使用键值版本 1:

```
VaultOperations operations = new VaultTemplate(new VaultEndpoint());
VaultKeyValueOperations keyValueOperations = operations.opsForKeyValue("secret",
							VaultKeyValueOperationsSupport.KeyValueBackend.KV_1);

keyValueOperations.put("elvis", Collections.singletonMap("password", "409-52-2002"));

VaultResponse read = keyValueOperations.get("elvis");
read.getRequiredData().get("social-security-number");
```

`VaultKeyValueOperations`支持所有键值操作，如`put`，`get`，`delete`，`list`。

或者，可以通过`VaultTemplate`使用该 API，因为其直接映射和简单的使用，因为键和响应直接映射到输入和输出键。下面的示例演示了在`mykey`处写和读一个秘密。`kv`秘密引擎安装在`secret`:

```
VaultOperations operations = new VaultTemplate(new VaultEndpoint());

operations.write("secret/elvis", Collections.singletonMap("social-security-number", "409-52-2002"));

VaultResponse read = operations.read("secret/elvis");
read.getRequiredData().get("social-security-number");
```

你可以在 Vault 参考文档中找到有关[Vault 键-value 版本 1API](https://www.vaultproject.io/api-docs/secret/kv/kv-v1)的更多详细信息。

### 10.2.键值版本 2（“版本管理的秘密”）

你可以在两个版本中的一个版本中运行`kv`秘密引擎。本节使用版本 2 进行说明。当运行版本 2 的`kv`后端时，一个键可以保留可配置的版本数量。你可以检索旧版本的元数据和数据。此外，你还可以使用检查和设置操作来避免无意中覆盖数据。

与[键值版本 1（“无版本的秘密”）](#vault.core.backends.kv1)类似， Spring Vault 附带了一个专用的键值 API，以封装各个键值 API 实现之间的差异。 Spring Vault 附带一个专用的键值 API，以封装各个键值 API 实现之间的差异。`VaultKeyValueOperations`遵循 Vault CLI 的设计。这是 Vault 的主要命令行工具，提供诸如`vault kv get`、`vault kv put`等命令。

通过指定版本和挂载路径，你可以将此 API 用于两个键值引擎版本。下面的示例使用键值版本 2:

```
VaultOperations operations = new VaultTemplate(new VaultEndpoint());
VaultKeyValueOperations keyValueOperations = operations.opsForKeyValue("secret",
							VaultKeyValueOperationsSupport.KeyValueBackend.KV_2);

keyValueOperations.put("elvis", Collections.singletonMap("social-security-number", "409-52-2002"));

VaultResponse read = keyValueOperations.get("elvis");
read.getRequiredData().get("social-security-number");
```

`VaultKeyValueOperations`支持所有键值操作，如`put`，`get`，`delete`，`list`。

你还可以与版本管理的键值 API 的具体内容进行交互。如果你想要获得特定的秘密或需要访问元数据，这是非常有用的。

```
VaultOperations operations = new VaultTemplate(new VaultEndpoint());
VaultVersionedKeyValueOperations versionedOperations = operations.opsForVersionedKeyValue("secret");

Versioned.Metadata metadata = versionedOperations.put("elvis",							(1)
					Collections.singletonMap("social-security-number", "409-52-2002"));

Version version = metadata.getVersion();												(2)

Versioned<Object> ssn = versionedOperations.get("elvis", Version.from(42));				(3)

Versioned<SocialSecurityNumber> mappedSsn = versionedOperations.get("elvis",			(4)
											Version.from(42), SocialSecurityNumber.class);

Versioned<Map<String,String>> versioned = Versioned.create(Collections					(5)
						.singletonMap("social-security-number", "409-52-2002"),
						Version.from(42));

versionedOperations.put("elvis", version);
```

|**1**|将秘密存储在`elvis`上，在`secret/`挂载下可用。|
|-----|---------------------------------------------------------------------------------------------------|
|**2**|将数据存储在版本控制的后端中，将返回元数据，例如版本号。|
|**3**|版本控制的键值 API 允许检索由版本号标识的特定版本。|
|**4**|版本控制的键值秘密可以映射到值对象中。|
|**5**|当使用 CAS 更新受版本控制的秘密时，输入必须引用先前获得的版本。|

而使用`kv`V2Secrets 引擎通过`VaultTemplate`是可能的。这不是最方便的方法，因为 API 提供了一种不同的方法来处理上下文路径以及如何表示输入/输出。具体地说，与实际秘密的交互需要对数据部分进行包装和解包装，并在挂载和秘密密钥之间引入`data/`路径段。

```
VaultOperations operations = new VaultTemplate(new VaultEndpoint());

operations.write("secret/data/elvis", Collections.singletonMap("data",
			Collections.singletonMap("social-security-number", "409-52-2002")));

VaultResponse read = operations.read("secret/data/ykey");
Map<String,String> data = (Map<String, String>) read.getRequiredData().get("data");
data.get("social-security-number");
```

你可以在 Vault 参考文档中找到有关[Vault 键-value2API](https://www.vaultproject.io/api-docs/secret/kv/kv-v2)的更多详细信息。

### 10.3.PKI（公开密钥基础设施）

`pki`Secrets 引擎通过实现证书颁发机构操作来表示证书的后端。

PKI 机密引擎生成动态 X.509 证书。使用这个秘密引擎，服务可以获得证书，而无需经过通常的手工过程，即生成私钥和 CSR，提交给 CA，并等待验证和签名过程完成。Vault 内置的身份验证和授权机制提供了验证功能。

Spring Vault 通过`VaultPkiOperations`支持证书的颁发、签名、撤销和 CRL 检索。所有其他的 PKI 功能都可以通过`VaultOperations`使用。

以下示例简要说明了如何颁发和撤销证书的用法：

```
VaultOperations operations = new VaultTemplate(new VaultEndpoint());
VaultPkiOperations pkiOperations = operations.opsForPki("pki");

VaultCertificateRequest request = VaultCertificateRequest.builder()								(1)
			.ttl(Duration.ofHours(48))
			.altNames(Arrays.asList("prod.dc-1.example.com", "prod.dc-2.example.com"))
			.withIpSubjectAltName("1.2.3.4")
			.commonName("hello.example.com")
			.build();

VaultCertificateResponse response = pkiOperations.issueCertificate("production", request); 		(2)
CertificateBundle certificateBundle = response.getRequiredData();

KeyStore keyStore = certificateBundle.createKeyStore("my-keystore");							(3)

KeySpec privateKey = certificateBundle.getPrivateKeySpec();										(4)
X509Certificate certificate = certificateBundle.getX509Certificate();
X509Certificate caCertificate = certificateBundle.getX509IssuerCertificate();

pkiOperations.revoke(certificateBundle.getSerialNumber());										(5)
```

|**1**|通过使用`VaultCertificateRequest`Builder 构建一个证书请求。|
|-----|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|**2**|从 Vault 请求证书。<br/>Vault 充当证书颁发机构，并使用签名的 X.509 证书进行响应。<br/>实际响应是`CertificateBundle`。|
|**3**|你可以直接获得生成的证书，作为包含公钥和私钥以及颁发者证书的 Java 密钥存储库。KeyStore 有广泛的用途，这使得这种格式适合于配置（例如，HTTP 客户机、数据库驱动程序或 SSL 安全的 HTTP 服务器）。|
|**4**|`CertificateBundle`允许直接通过 Java Cryptography Extension API 访问私钥以及公共和发行者证书。|
|**5**|一旦一个证书不再使用（或者它已被破坏），你可以通过它的序列号来撤销它。<br/>Vault 在其 CRL 中包含了已撤销的证书。|

你可以在 Vault 参考文档中找到有关[Vault PKI 机密 API](https://www.vaultproject.io/api-docs/secret/pki)的更多详细信息。

### 10.4.令牌认证后端

此后端是不与实际秘密交互的身份验证后端。相反，它提供了访问令牌管理的访问权限。你可以在[认证方法章节](#vault.core.authentication)中阅读有关[基于令牌的身份验证](#vault.authentication.token)的更多信息。

`token`身份验证方法是内置的，并且在`/auth/token`自动可用。它允许用户使用令牌进行身份验证，以及创建新令牌、通过令牌撤销秘密等等。

当任何其他 auth 方法返回一个标识时，Vault Core 调用令牌方法为该标识创建一个新的唯一令牌。

你还可以使用令牌存储来绕过任何其他的 auth 方法。你可以直接创建令牌，也可以对令牌执行各种其他操作，例如更新和撤销。

Spring Vault 使用此后端来更新和撤销由配置的[认证方法](#vault.core.authentication)提供的会话令牌。

以下示例展示了如何从应用程序中请求、更新和撤销 Vault 令牌：

```
VaultOperations operations = new VaultTemplate(new VaultEndpoint());
VaultTokenOperations tokenOperations = operations.opsForToken();

VaultTokenResponse tokenResponse = tokenOperations.create();                          (1)
VaultToken justAToken = tokenResponse.getToken();

VaultTokenRequest tokenRequest = VaultTokenRequest.builder().withPolicy("policy-for-myapp")
									.displayName("Access tokens for myapp")
									.renewable()
									.ttl(Duration.ofHours(1))
									.build();

VaultTokenResponse appTokenResponse = tokenOperations.create(tokenRequest);          (2)
VaultToken appToken = appTokenResponse.getToken();

tokenOperations.renew(appToken);                                                     (3)

tokenOperations.revoke(appToken);                                                    (4)
```

|**1**|通过应用角色默认值来创建令牌。|
|-----|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|**2**|使用 Builder API，你可以为要请求的令牌定义细粒度的设置。<br/>请求令牌将返回`VaultToken`，该对象用于 Vault 令牌的值对象。|
|**3**|你可以通过令牌 API 更新令牌。通常，这是通过`SessionManager`来完成的，以保持对保险库会话令牌的跟踪。|
|**4**|如果需要，可以通过令牌 API 撤销令牌。通常，这是通过`SessionManager`来完成的，以保持对保险库会话令牌的跟踪。|

你可以在 Vault 参考文档中找到有关[Vault Token Auth 方法 API](https://www.vaultproject.io/api-docs/auth/token)的更多详细信息。

### 10.5.传输后端

传输秘密引擎处理传输中数据的加密功能。Vault 不存储发送到这个秘密引擎的数据。它也可以被看作是“加密作为一种服务”或“加密作为一种服务”。Transit Secrets 引擎还可以对数据进行签名和验证，生成数据的散列和 HMAC，并充当随机字节源。

Transit 的主要用例是对来自应用程序的数据进行加密，同时仍将加密的数据存储在一些主数据存储中。这减轻了应用程序开发人员进行适当加密和解密的负担，并将负担推给了 Vault 的运营商。

Spring Vault 支持广泛的中转操作：

* 密钥创建

* 密钥重新配置

* 加密/解密/重新包装

* HMAC 计算

* 签名和签名验证

`transit`中的所有操作都以键为中心。Transit 引擎支持键和[各种关键类型](https://www.vaultproject.io/docs/secrets/transit)的版本控制。请注意，键类型可能会对可以使用的操作施加限制。

以下示例展示了如何创建密钥以及如何对数据进行加密和解密：

```
VaultOperations operations = new VaultTemplate(new VaultEndpoint());
VaultTransitOperations transitOperations = operations.opsForTransit("transit");

transitOperations.createKey("my-aes-key", VaultTransitKeyCreationRequest.ofKeyType("aes128-gcm96"));	(1)

String ciphertext = transitOperations.encrypt("my-aes-key", "plaintext to encrypt");					(2)

String plaintext = transitOperations.decrypt("my-aes-key", ciphertext);									(3)
```

|**1**|首先，我们需要一个以.<br/>开头的键，每个键都需要指定的类型。`aes128-gcm96`支持加密、解密、密钥派生和收敛加密，在此示例中，我们需要对其进行加密和解密。|
|-----|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|**2**|接下来，我们对包含应该加密的纯文本的`String`进行加密，<br/>输入`String`使用默认的`Charset`将字符串编码为其二进制表示，<br/>请求令牌将返回`VaultToken`，它被用作 Vault 令牌的值对象。<br/>`encrypt`方法返回 base64 编码的密文，通常从`vault:`开始。|
|**3**|要将密文解密为纯文本，请调用`decrypt`方法。<br/>它会解密密文并返回一个`String`，并使用默认字符集对其进行解码。|

前面的示例使用简单的字符串进行加密操作。虽然它是一种简单的方法，但它有 charset 错误配置的风险，并且不是二进制安全的。当纯文本对数据（如图像、压缩数据或二进制数据结构）使用二进制表示时，需要二进制安全性。

要对二进制数据进行加密和解密，请使用`Plaintext`和`Ciphertext`值对象，这些对象可以保存二进制值：

```
byte [] plaintext = "plaintext to encrypt".getBytes();

Ciphertext ciphertext = transitOperations.encrypt("my-aes-key", Plaintext.of(plaintext));			(1)

Plaintext decrypttedPlaintext = transitOperations.decrypt("my-aes-key", ciphertext);				(2)
```

|**1**|假设密钥`my-aes-key`已经存在，我们正在加密`Plaintext`对象。<br/>作为回报，`encrypt`方法返回一个`Ciphertext`对象。|
|-----|------------------------------------------------------------------------------------------------------------------------------------------------------------|
|**2**|`Ciphertext`对象可以直接用于解密，并返回`Plaintext`对象。|

`Plaintext`和`Ciphertext`带有一个上下文对象，`VaultTransitContext`。它用于为[收敛加密](https://www.vaultproject.io/docs/secrets/transit#convergent-encryption)提供一个 nonce 值，并为一个上下文值提供一个使用键派生的值。

Transit 允许对纯文本进行签名并验证给定纯文本的签名。符号操作需要一个不对称的密钥，通常使用椭圆曲线加密或 RSA。

|   |签名使用公钥/私钥分割来确保真实性。<br/>签名者使用其私钥创建签名。否则，任何人都可以以你的名义对消息进行签名。<br/>验证者使用公钥部分来验证签名。实际的签名通常是一个散列值。<br/><br/>在内部，散列将使用私钥进行计算和加密，以创建最终签名。验证将解密签名消息，计算它们自己的纯文本散列，并比较两个散列值以检查签名是否有效。|
|---| |

```
byte [] plaintext = "plaintext to sign".getBytes();

transitOperations.createKey("my-ed25519-key", VaultTransitKeyCreationRequest.ofKeyType("ed25519"));	(1)

Signature signature = transitOperations.sign("my-ed25519-key", Plaintext.of(plaintext));			(2)

boolean valid = transitOperations.verify("my-ed25519-key", Plaintext.of(plaintext), signature);		(3)
```

|**1**|签名需要一个不对称的密钥。你可以使用任何椭圆曲线加密或 RSA 密钥类型。一旦创建了密钥，你就拥有了创建签名的所有先决条件。|
|-----|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|**2**|为纯文本消息创建签名。返回的`Signature`包含一个使用 base64 字符的 ASCII 安全字符串。|
|**3**|要验证签名，验证需要一个签名对象和纯文本消息。作为返回值，你将得到签名是否有效。|

你可以在 Vault 参考文档中找到有关[Vault Transit 后端](https://www.vaultproject.io/api/secret/transit)的更多详细信息。

## 11.ReactiveVaultTemplate 简介

本节涵盖了关于使用 Spring Vault 的反应式编程支持的基本信息。

### 11.1.什么是反应式编程？

简单地说，反应式编程是关于非阻塞的应用程序，它们是异步和事件驱动的，并且需要少量线程来垂直扩展（即在 JVM 内），而不是水平扩展（即通过集群）。

反应性应用程序的一个关键方面是反压力的概念，这是一种确保生产者不会压倒消费者的机制。例如，在从数据库扩展到 HTTP 响应的反应性组件的管道中，当 HTTP 连接太慢时，数据存储库也可以减慢速度或完全停止，直到网络容量释放出来。

### 11.2.反应式保险库客户端

Spring Vault 的反应性客户端支持是建立在[可组合身份验证步骤](#vault.authentication.steps)和 Spring 的功能性`WebClient`之上的，通过 Reactor 内蒂 或 Jetty，这两个功能都具有完全非阻塞的、事件驱动的 HTTP 客户端。

它将`VaultTokenSupplier`作为`VaultToken`的供应商公开以验证 HTTP 请求，并将`ReactiveVaultOperations`作为主要入口点。`VaultEndpoint`、`ClientOptions`和[SSL](#vault.client-ssl)的核心配置在各种客户机实现中被重用。

类`ReactiveVaultTemplate`位于包`org.springframework.vault.core`中，是 Spring 的 Reactive Vault 支持的中心类，提供了与 Vault 交互的丰富功能集。该模板提供了在 Vault 中读、写和删除数据的方便操作，并提供了域对象和 Vault 数据之间的映射。

|   |一旦配置完成，`ReactiveVaultTemplate`是线程安全的，并且可以在<br/>多个实例中重用。|
|---|--------------------------------------------------------------------------------------------------------|

Vault 文档和域类之间的映射是通过委托给`WebClient`及其编解码器来完成的。

`ReactiveVaultTemplate`类实现了接口`ReactiveVaultOperations`。在尽可能多的情况下，`ReactiveVaultOperations`上的方法是以 Vault API 上可用的方法命名的，以使熟悉 API 和 CLI 的现有 Vault 开发人员熟悉该 API。例如，你将找到诸如“write”、“delete”和“read”之类的方法。设计目标是使 Vault API 的使用和`ReactiveVaultOperations`之间的转换变得尽可能容易。这两个 API 之间的一个主要区别是，`ReactiveVaultOperations`可以传递域对象，而不是 JSON 键-值对。

|   |引用`ReactiveVaultTemplate`实例<br/>上的操作的首选方法是通过其接口`ReactiveVaultOperations`。|
|---|-------------------------------------------------------------------------------------------------------------------------------------|

`ReactiveVaultTemplate`未显式公开的功能你可以使用几种执行回调方法中的一种来访问底层 API。Execute 回调将为你提供对`WebClient`对象的引用。有关更多信息，请参见[执行回调](#vault.core.reactive.executioncallback)一节。

现在，让我们来看看如何在 Spring 容器的上下文中使用 Vault 的示例。

### 11.3.注册和配置 Spring Vault bean

使用 Spring vault 不需要 Spring 上下文。然而，在托管上下文中注册的`ReactiveVaultTemplate`和`VaultTokenSupplier`的实例将参与由 Spring IOC 容器提供的[生命周期事件](https://docs.spring.io/spring/docs/5.3.4/spring-framework-reference/core.html#beans-factory-nature)。这对于在应用程序关闭时处理活动的 Vault 会话非常有用。你还受益于在应用程序中重用相同的`ReactiveVaultTemplate`实例。

Spring Vault 附带了一个支持配置类，该配置类提供了 Bean 用于在 Spring 上下文中使用的定义。应用程序配置类通常从`AbstractVaultConfiguration`扩展，并且需要提供环境特定的附加细节。

从`AbstractVaultConfiguration`扩展需要实现` VaultEndpoint vaultEndpoint()`和`ClientAuthentication clientAuthentication()`方法。

例 9.使用基于 Java 的 Bean 元数据注册 Spring Vault 对象

```
@Configuration
public class AppConfig extends AbstractReactiveVaultConfiguration {

    /**
     * Specify an endpoint for connecting to Vault.
     */
    @Override
    public VaultEndpoint vaultEndpoint() {
        return new VaultEndpoint();                            (1)
    }

    /**
     * Configure a client authentication.
     * Please consider a more secure authentication method
     * for production use.
     */
    @Override
    public ClientAuthentication clientAuthentication() {
        return new TokenAuthentication("…");                   (2)
    }
}
```

|**1**|创建一个新的`VaultEndpoint`，默认情况下指向`https://localhost:8200`。|
|-----|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|**2**|此示例使用`TokenAuthentication`快速启动。<br/>有关支持的身份验证方法的详细信息，请参见[认证方法](#vault.core.authentication)。|

### 11.4.会话管理

Spring Vault 需要令牌来验证 Vault 请求。有关身份验证的详细信息，请参见[认证方法](#vault.core.authentication)。反应式客户端需要一个非阻塞令牌供应商，其契约定义在`VaultTokenSupplier`中。令牌可以是静态的，也可以通过[声明的身份验证流程](#vault.authentication.steps)获得。Vault 登录不应该发生在每个经过身份验证的 Vault 交互上，但是会话令牌应该在会话上保存。该方面由实现`ReactiveSessionManager`的会话管理器处理，例如`ReactiveLifecycleAwareSessionManager`。

### 11.5.执行回调

Spring 所有模板类的一个常见设计特征是，所有功能都被路由到一个模板中执行回调方法。这有助于确保执行异常和可能需要的任何资源管理的一致性。虽然在 JDBC 和 JMS 的情况下，这比 Vault 的需要大得多，但它仍然为访问和日志记录的发生提供了一个单一的位置。因此，使用 Execute 回调是访问 Vault API 的首选方式，以执行我们在`ReactiveVaultTemplate`上没有作为方法公开的不常见操作。

下面是执行回调方法的列表。

* `<T> T`**DowithVault**`(Function<WebClient, ? extends T> clientCallback)`组成给定的反应序列`WebClient`，允许在没有会话上下文的情况下与 Vault 进行交互。

* 会话`<T> T`**首次会议**`(Function<WebClient, ? extends T> clientCallback)`组成给定的反应序列`WebClient`，允许在经过身份验证的保险库中进行交互。

下面是一个使用回调来初始化 Vault 的示例：

```
reactiveVaultOperations.doWithVault(webClient -> {

    return webClient.put()
                    .uri("/sys/init")
                    .syncBody(request)
                    .retrieve()
                    .toEntity(VaultInitializationResponse.class);
});
```

## 12.保险库财产来源支持

保险库可以有许多不同的使用方式。一个特定的用例是使用 Vault 存储加密的属性。 Spring Vault 支持 Vault 作为属性源，以使用 Spring 的[PropertySource 抽象](https://docs.spring.io/spring/docs/5.3.4/spring-framework-reference/core.html#beans-property-source-abstraction)获得配置属性。

|   |你可以引用存储在 Vault 中的其他属性源中的属性，或者使用`@Value(…)`的值注入。当引导需要存储在保险库中的数据的 bean 时，需要特别注意。此时必须初始化`VaultPropertySource`才能从 Vault 检索属性。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

|   |Spring 引导/ Spring 云用户可以受益于[Spring Cloud Vault](https://github.com/spring-cloud/spring-cloud-vault-config)的<br/>配置集成，该集成在应用程序启动期间初始化各种属性源。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

### 12.1.注册`VaultPropertySource`

Spring Vault 提供了一个`VaultPropertySource`以与 Vault 一起使用来获得属性。它使用嵌套的`data`元素公开在 Vault 中存储和加密的属性。

```
ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new VaultPropertySource(vaultTemplate, "secret/my-application"));
```

在上面的代码中，`VaultPropertySource`在搜索中被添加了最高优先级。如果它包含一个 foo` property, it will be detected and returned ahead of any `foo` property in any other `PropertySource`.`mutablePropertySources` 暴露了许多方法，这些方法允许对属性源集进行精确操作。

### 12.2.@VaultPropertySource

`@VaultPropertySource`注释提供了一种方便的声明性机制，用于将`PropertySource`添加到 Spring 的`Environment`中，以便与`@Configuration`类一起使用。

`@VaultPropertySource`采用 vault 路径，如`secret/my-application`，并公开存储在节点`PropertySource`中的数据。`@VaultPropertySource`支持与租赁相关的秘密的租赁续订（即来自`mysql`后端的凭据）和在终端租赁到期时的凭据旋转。默认情况下，租约续订是禁用的。

例 10.存储在保险库中的属性

```
{
  // …

  "data": {
    "database": {
      "password": ...
    },
    "user.name": ...,
  }

  // …
}
```

例 11.声明`@VaultPropertySource`

```
@Configuration
@VaultPropertySource("secret/my-application")
public class AppConfig {

    @Autowired Environment env;

    @Bean
    public TestBean testBean() {
        TestBean testBean = new TestBean();
        testBean.setUser(env.getProperty("user.name"));
        testBean.setPassword(env.getProperty("database.password"));
        return testBean;
    }
}
```

例 12.声明带有凭据旋转和前缀的`@VaultPropertySource`

```
@Configuration
@VaultPropertySource(value = "aws/creds/s3-access",
                     propertyNamePrefix = "aws.",
                     renewal = Renewal.ROTATE)
public class AppConfig {
  // provides aws.access_key and aws.secret_key properties
}
```

|   |从`generic`秘密后端获得的秘密与 TTL（`refresh_interval`）相关联，但不是租赁 ID。 Spring Vault 的`PropertySource`在到达其 TTL 时会旋转通用秘密。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

|   |你可以使用`@VaultPropertySource`从版本控制的键值后端获得最新的秘密版本。确保路径中不包含`data/`段。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------|

在`@VaultPropertySource`路径中存在的任何`${…​}`占位符都将根据已经针对该环境注册的一组属性源进行解析，如下例所示：

例 13.使用占位符声明`@VaultPropertySource`路径

```
@Configuration
@VaultPropertySource(value = "aws/creds/${my.placeholder:fallback/value}",
                     propertyNamePrefix = "aws.",
                     renewal = Renewal.ROTATE)
public class AppConfig {
}
```

假设`my.placeholder`存在于已经注册的一个属性源中（例如，系统属性或环境变量），则将占位符解析为相应的值。如果不是，则将`fallback/value`用作默认值。如果没有指定默认值，并且无法解析某个属性，则抛出一个`IllegalArgumentException`。

在某些情况下，当使用`@VaultPropertySource`注释时，严格控制属性源排序可能是不可能的或不实用的。例如，如果上面的`@Configuration`类是通过组件扫描注册的，那么排序是很难预测的。在这种情况下（如果重写很重要），建议用户回到使用 PropertySource API。详见[`ConfigurableEnvironment`]（https://DOCS. Spring.io/ Spring-framework/DOCS/current/javadoc-api/org/springframework/core/core/ENV/confirablebletermnirtonment.html）和[<gtr="527"/>]（https://DOCS. Spring.io/ Spring/DOCS/current/javadoc-api/org/springframework/core/env/mutablepropertysources.html））

## 13.保险库

使用`VaultTemplate`和映射到 Java 类的响应可以实现基本的数据操作，如读、写和删除。 Spring Vault 存储库在 Vault 之上应用了数据存储库的概念。Vault 存储库公开了基本的增删改查功能，并支持使用限制 ID 属性、分页和排序的谓词进行查询派生。

|   |在[Spring Data Commons reference documentation](https://docs.spring.io/spring-data/commons/docs/current/reference/html/#repositories)中阅读有关 Spring 数据存储库的更多信息。参考文档将向你介绍 Spring 数据存储库。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

### 13.1.用法

要访问存储在 Vault 中的域实体，你可以利用存储库支持，从而大大简化这些实现。

例 14.示例凭据实体

```
@Secret
public class Credentials {

  @Id String id;
  String password;
  String socialSecurityNumber;
  Address address;
}
```

这里有一个非常简单的域对象。请注意，它有一个名为`id`的属性，并对其类型进行了`org.springframework.data.annotation.Id`注释和`@Secret`注释。这两个人负责创建用于在 Vault 内部将对象持久化为 JSON 的实际密钥。

|   |用`@Id`注释的属性以及那些名为`id`的属性被视为标识符属性。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------|

下一步是声明一个使用域对象的存储库接口。

例 15.`Credentials`实体的基本存储库接口

```
public interface CredentialsRepository extends CrudRepository<Credentials, String> {

}
```

当我们的存储库扩展`CrudRepository`时，它提供了基本的增删改查和查询方法。保险库需要 Spring 个数据组件。确保在类路径中包含`spring-data-commons`和`spring-data-keyvalue`工件。

要实现这一点，最简单的方法是设置依赖管理，并将工件添加到`pom.xml`:

然后将以下内容添加到`pom.xml`依赖关系部分。

例 16.使用 Spring 数据 BOM

```
<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework.data</groupId>
      <artifactId>spring-data-bom</artifactId>
      <version>2020.0.2</version>
      <scope>import</scope>
      <type>pom</type>
    </dependency>
  </dependencies>
</dependencyManagement>

<dependencies>

  <!-- other dependency elements omitted -->

  <dependency>
    <groupId>org.springframework.vault</groupId>
    <artifactId>spring-vault-core</artifactId>
    <version>2.3.1</version>
  </dependency>

  <dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-keyvalue</artifactId>
    <!-- Version inherited from the BOM -->
  </dependency>

</dependencies>
```

我们需要在两者之间将东西粘在一起的东西是根据 Spring 配置的。

例 17.JavaConfig 用于保险库存储库

```
@Configuration
@EnableVaultRepositories
public class ApplicationConfig {

  @Bean
  public VaultTemplate vaultTemplate() {
    return new VaultTemplate(…);
  }
}
```

鉴于上述设置，我们可以继续并注入`CredentialsRepository`到我们的组件。

例 18.访问个人实体

```
@Autowired CredentialsRepository repo;

public void basicCrudOperations() {

  Credentials creds = new Credentials("heisenberg", "327215", "AAA-GG-SSSS");
  rand.setAddress(new Address("308 Negra Arroyo Lane", "Albuquerque", "New Mexico", "87104"));

  repo.save(creds);                                        (1)

  repo.findOne(creds.getId());                             (2)

  repo.count();                                            (3)

  repo.delete(creds);                                      (4)
}
```

|**1**|将`Credentials`的属性以键模式`keyspace/id`、<br/>存储在 Vault Hash 中，在本例中，将`credentials/heisenberg`的属性存储在通用秘密后端中。|
|-----|----------------------------------------------------------------------------------------------------------------------------------------------------------------|
|**2**|使用提供的 ID 来检索存储在`keyspace/id`的对象。|
|**3**|计算在`Credentials`上由`@Secret`定义的键位*证书*内可用的实体总数。|
|**4**|从保险库中删除给定对象的密钥。|

### 13.2.对象到 Vault JSON 映射

Vault 存储库使用 JSON 作为交换格式在 Vault 中存储对象。JSON 和实体之间的对象映射是通过`VaultConverter`完成的。转换器读取和写入`SecretDocument`中包含来自`VaultResponse`的主体的`SecretDocument`。从 Vault 读取`VaultResponse`s，并通过 Jackson 将正文反序列化为`Map`的`String`和`Object`。默认的`VaultConverter`实现读取带有嵌套的值`Map`和`List`的`Map`对象，并将这些对象转换为实体，反之亦然。

给定前几节中的`Credentials`类型，默认映射如下：

```
{
  "_class": "org.example.Credentials",                 (1)
  "password", "327215",                                (2)
  "socialSecurityNumber": "AAA-GG-SSSS",
  "address": {                                         (3)
    "street": "308 Negra Arroyo Lane",
    "city": "Albuquerque",
    "state": "New Mexico",
    "zip":"87104"
  }
}
```

|**1**|`_class`属性包含在根级别以及任何嵌套接口或抽象类型上。|
|-----|------------------------------------------------------------------------------------------------------|
|**2**|简单属性值由路径映射。|
|**3**|复杂类型的属性被映射为嵌套对象。|

|   |`@Id`属性必须映射到`String`。|
|---|----------------------------------------------|

|              Type              |样本|                   Mapped Value                    |
|--------------------------------|--------------------------------------------------------|---------------------------------------------------|
| Simple Type  <br/>(eg. String) |string firstname=“walter”；|               firstname = "Walter"                |
|Complex Type  <br/>(eg. Address)|Address=New Address（“308Negra Arroyo Lane”）；|  address: { "street": "308 Negra Arroyo Lane" }   |
|   List  <br/>of Simple Type    |list\<String\>nicknames=aslist（“Walt”，“Heisenberg”）；|         nicknames: ["walt", "heisenberg"]         |
|    Map  <br/>of Simple Type    |map\<String, Integer\>atts=asmap（“年龄”，51）|                atts : {"age" : 51}                |
|   List  <br/>of Complex Type   |list\<Address\>addresses=aslist（new address（"308…|address: [{ "street": "308 Negra Arroyo Lane" }, …]|

你可以通过在`VaultCustomConversions`中注册`Converter`来定制映射行为。这些转换器可以处理从/转换为诸如`LocalDate`和`SecretDocument`之类的类型，而第一个转换器适合于将简单的属性和最后一个复杂的类型转换为它们的 JSON 表示。第二个选项提供对结果`SecretDocument`的完全控制。将对象写入`Vault`将删除内容并重新创建整个条目，因此未映射的数据将丢失。

### 13.3.查询和查询方法

查询方法允许从方法名自动派生简单的查询。Vault 没有查询引擎，但需要直接访问 HTTP 上下文路径。Vault 查询方法将 Vault 的 API 可能性转换为查询。查询方法执行在上下文路径下列出子项，对 ID 应用筛选，可选地使用偏移量/限制限制限制 ID 流，并在获取结果后应用排序。

例 19.样本库查询方法

```
public interface CredentialsRepository extends CrudRepository<Credentials, String> {

  List<Credentials> findByIdStartsWith(String prefix);
}
```

|   |Vault 存储库的查询方法仅支持带有`@Id`属性上的谓词的查询。|
|---|------------------------------------------------------------------------------------------------|

下面是 Vault 支持的关键字的概述。

|              Keyword               |样本|
|------------------------------------|-------------------------------------------------------------|
|       `After`, `GreaterThan`       |`findByIdGreaterThan(String id)`|
|         `GreaterThanEqual`         |`findByIdGreaterThanEqual(String id)`|
|        `Before`, `LessThan`        |`findByIdLessThan(String id)`|
|          `LessThanEqual`           |`findByIdLessThanEqual(String id)`|
|             `Between`              |`findByIdBetween(String from, String to)`|
|                `In`                |`findByIdIn(Collection ids)`|
|              `NotIn`               |`findByIdNotIn(Collection ids)`|
|`Like`, `StartingWith`, `EndingWith`|`findByIdLike(String id)`|
|       `NotLike`, `IsNotLike`       |`findByIdNotLike(String id)`|
|            `Containing`            |`findByFirstnameContaining(String id)`|
|          `NotContaining`           |`findByFirstnameNotContaining(String name)`|
|              `Regex`               |`findByIdRegex(String id)`|
|           `(No keyword)`           |`findById(String name)`|
|               `Not`                |`findByIdNot(String id)`|
|               `And`                |`findByLastnameAndFirstname`|
|                `Or`                |`findByLastnameOrFirstname`|
|            `Is,Equals`             |`findByFirstname`,`findByFirstnameIs`,`findByFirstnameEquals`|
|            `Top,First`             |`findFirst10ByFirstname`,`findTop5ByFirstname`|

#### 13.3.1.分类和分页

查询方法通过在内存中选择从保险库上下文路径检索的子列表 ID 来支持排序和分页。与查询方法谓词不同，排序不限于特定字段。在进行 ID 过滤后，将应用未分页的排序，并从保险库中获取所有产生的秘密。通过这种方式，查询方法只获取作为结果的一部分返回的结果。

使用分页和排序需要在过滤 ID 之前进行秘密获取，这会影响性能。排序和分页保证返回相同的结果，即使 Vault 返回的 ID 的 Natural Order 发生了变化。因此，首先从 Vault 获取所有 ID，然后应用排序，然后进行过滤和偏移/限制。

例 20.分页和排序存储库

```
public interface CredentialsRepository extends PagingAndSortingRepository<Credentials, String> {

  List<Credentials> findTop10ByIdStartsWithOrderBySocialSecurityNumberDesc(String prefix);

  List<Credentials> findByIdStarts(String prefix, Pageable pageRequest);
}
```

## 14.客户支持

Spring Vault 支持各种 HTTP 客户端访问 Vault 的 HTTP API。 Spring Vault 使用[`RestTemplate`]（https://DOCS. Spring.io/ Spring/DOCS/5.3.4/ Spring-framework-reference/integration.html#rest-resttemplate）作为访问 Vault 的主要接口。专用的客户机支持源自[定制的 SSL 配置](#vault.client-ssl)，其作用域仅限于 Spring Vault 的客户机组件。

Spring Vault 支持以下 HTTP 命令式客户端：

* Java 的内置`HttpURLConnection`（默认客户端）

* Apache HTTP 组件

* Netty

* OKHTTP3

Spring Vault 的反应性集成支持以下反应性 HTTP 客户端：

* 反应堆网状结构

* Jetty

使用特定的客户端需要在 Classpath 上可用的相应的依赖关系，因此 Spring Vault 可以使用可用的客户端与 Vault 进行通信。

### 14.1.Java 的内置`HttpURLConnection`

Java 的内置`HttpURLConnection`是开箱即用的，不需要额外的配置。使用`HttpURLConnection`有一个关于 SSL 配置的限制。 Spring Vault 将不适用[定制的 SSL 配置](#vault.client-ssl)，因为它将需要对 JVM 进行深度重新配置。这种配置将影响依赖默认 SSL 上下文的所有组件。使用`HttpURLConnection`配置 SSL 设置需要你将这些设置作为系统属性提供。有关更多详细信息，请参见[定制 JSSE](https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html#InstallationAndCustomization)。

### 14.2.外部客户

你可以使用外部客户机访问 Vault 的 API。只需向你的项目添加以下依赖项之一。如果使用[Spring Vault’s Dependency BOM](#dependencies)，则可以省略版本号。

例 21. Apache HTTP 组件依赖关系

```
<dependency>
  <groupId>org.apache.httpcomponents</groupId>
  <artifactId>httpclient</artifactId>
</dependency>
```

|   |Apache HttpClient 的[电汇测井](https://hc.apache.org/httpcomponents-client-4.5.x/logging.html)可以通过日志配置来启用。确保不会意外地启用有线日志，因为日志可能会以纯文本的形式暴露应用程序和保险库之间的流量（令牌和秘密）。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

例 22.内蒂依赖

```
<dependency>
  <groupId>io.netty</groupId>
  <artifactId>netty-all</artifactId>
</dependency>
```

例 23.Square OkHTTP3

```
<dependency>
  <groupId>com.squareup.okhttp3</groupId>
  <artifactId>okhttp</artifactId>
</dependency>
```

例 24.反应堆网状结构

```
<dependency>
  <groupId>io.projectreactor.netty</groupId>
  <artifactId>reactor-netty</artifactId>
</dependency>
```

例 25. Jetty

```
<dependency>
  <groupId>org.eclipse.jetty</groupId>
  <artifactId>jetty-reactive-httpclient</artifactId>
</dependency>
```

### 14.3.Vault 客户端 SSL 配置

通过设置各种属性，可以使用`SslConfiguration`配置 SSL。你可以设置`javax.net.ssl.trustStore`来配置 JVM 范围内的 SSL 设置，也可以设置`SslConfiguration`来仅为 Spring Vault 设置 SSL 设置。

```
SslConfiguration sslConfiguration = SslConfiguration.create(            (1)
		new FileSystemResource("client-cert.jks"), "changeit".toCharArray(),
		new FileSystemResource("truststore.jks"), "changeit".toCharArray());

SslConfiguration.forTrustStore(new FileSystemResource("keystore.jks"),  (2)
                                      "changeit".toCharArray())

SslConfiguration.forKeyStore(new FileSystemResource("keystore.jks"),    (3)
                                      "changeit".toCharArray())

SslConfiguration.forKeyStore(new FileSystemResource("keystore.jks"),    (4)
                                      "changeit".toCharArray(),
                                      KeyConfiguration.of("key-password".toCharArray(),
                                      "my-key-alias"))
```

|**1**|全配置。|
|-----|-----------------------------------------------------------------------|
|**2**|只配置信任存储区设置。|
|**3**|只配置密钥存储区设置。|
|**4**|只配置密钥存储区设置并提供密钥配置。|

请注意，提供`SslConfiguration`仅在 Apache HTTP 组件或 OKHTTP 客户端位于你的类路径上时才能应用。

SSL 配置还支持 PEM 编码的证书，以替代 Java 密钥存储区。

```
KeyStoreConfiguration keystore = KeyStoreConfiguration
        .of(new ClassPathResource("ca.pem")).withStoreType("PEM");
SslConfiguration configuration = SslConfiguration.forTrustStore(keystore);
```

PEM 文件可以包含一个或多个证书（块`-----BEGIN CERTIFICATE-----`和`-----END CERTIFICATE-----`）。添加到底层`KeyStore`的证书使用完整的主题名称作为别名。

## 15.认证方法

不同的组织对安全性和身份验证有不同的要求。Vault 通过提供多种身份验证方法来反映这种需求。 Spring Vault 支持多种身份验证机制。

### 15.1.外部化登录凭据

获得对安全系统的首次访问称为安全引入。任何客户都需要短暂或永久的凭据才能访问 Vault。外部化凭据是保持代码可维护性高的一种很好的模式，但有可能增加披露的风险。

向任何一方披露登录凭据都允许登录到保险库并访问基础角色允许的秘密。选择适当的客户机身份验证并将凭据注入应用程序将受到风险评估的影响。

Spring 的[PropertySource 抽象](https://docs.spring.io/spring/docs/5.3.4/spring-framework-reference/core.html#beans-property-source-abstraction)是将配置保持在应用程序代码之外的一种自然适合。你可以使用系统属性、环境变量或属性文件来存储登录凭据。每种方法都有自己的特性。请记住，可以通过适当的 OS 访问级别来内省命令行和环境属性。

例 26.将`vault.token`外部化到属性文件

```
@PropertySource("configuration.properties")
@Configuration
public class Config extends AbstractVaultConfiguration {

    @Override
    public ClientAuthentication clientAuthentication() {
        return new TokenAuthentication(getEnvironment().getProperty("vault.token"));
    }
}
```

|   |Spring 允许以多种方式获得`Environment`。当使用`VaultPropertySource`时，通过`@Autowired Environment environment`注入将不会提供`Environment`，因为环境 Bean 仍在构建中，并且自动布线在较晚的阶段到来。你的配置类应该实现`ApplicationContextAware`，并从`ApplicationContext`获得`Environment`。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

参见[`SecurePropertyUsage.java`]（https://github.com/ Spring-projects/ Spring-vault/blob/master/ Spring-vault-core/SRC/test/java/org/springframework/vault/demo/securepropertyusage.java），以获取在组件和其他属性源中引用属性的示例。

### 15.2.令牌认证

令牌是在 Vault 中进行身份验证的核心方法。令牌身份验证需要提供一个静态令牌。

|   |令牌身份验证是默认的身份验证方法。<br/>如果令牌被公开为非预期的一方，则它获得对 Vault 的访问权限，并且<br/>可以为预期的客户端访问秘密。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

通常，令牌身份验证用于在外部创建和更新令牌的场景中（例如[HashiCorpVault Service Broker](https://github.com/hashicorp/vault-service-broker)）。根据实际的设置，你可能希望也可能不希望令牌更新和撤销。有关 TTL 和令牌撤销的详细信息，请参见[`LifecycleAwareSessionManager`]（#vault.authentication.会话）。

```
@Configuration
class AppConfig extends AbstractVaultConfiguration {

    // …

    @Override
    public ClientAuthentication clientAuthentication() {
        return new TokenAuthentication("…");
    }

    // …
}
```

另见：

* [保险库文档：令牌](https://www.vaultproject.io/docs/concepts/tokens.html)

* [Vault 文档：使用令牌身份验证后端](https://www.vaultproject.io/docs/auth/token.html)

### 15.3.APPID 身份验证

|   |Appid 身份验证被 Vault 反对。用[Approle 身份验证](#vault.authentication.approle)代替。|
|---|-----------------------------------------------------------------------------------------------------------------|

Vault 支持[AppId](https://www.vaultproject.io/docs/auth/app-id.html)身份验证，该验证由两个难以猜测的令牌组成。APPID 默认为静态配置的`spring.application.name`。第二个标记是 userid，它是由应用程序决定的一部分，通常与运行时环境相关。IP 地址、MAC 地址或 Docker 容器名称都是很好的例子。 Spring Vault 支持 IP 地址、MAC 地址和静态用户 ID（例如，通过系统属性提供）。IP 和 MAC 地址表示为十六进制编码的 SHA256 散列。

基于 IP 地址的用户 ID 使用本地主机的 IP 地址。

```
@Configuration
class AppConfig extends AbstractVaultConfiguration {

    // …

    @Override
    public ClientAuthentication clientAuthentication() {
        AppIdAuthenticationOptions options = AppIdAuthenticationOptions.builder()
                .appId("myapp")
                .userIdMechanism(new IpAddressUserId())
                .build();

        return new AppIdAuthentication(options, restOperations());
    }

    // …
}
```

从命令行生成 IP 地址 userid 的相应命令是：

```
$ echo -n 192.168.99.1 | sha256sum
```

|   |包括`echo`的换行将导致不同的散列值<br/>，因此请确保包括`-n`标志。|
|---|-------------------------------------------------------------------------------------------------------------|

基于 MAC 地址的用户 ID 从本地主机绑定的设备获得他们的网络设备。该配置还允许指定`network-interface`提示来选择正确的设备。`network-interface`的值是可选的，可以是接口名称或接口索引（基于 0）。

```
@Configuration
class AppConfig extends AbstractVaultConfiguration {

    // …

    @Override
    public ClientAuthentication clientAuthentication() {

        AppIdAuthenticationOptions options = AppIdAuthenticationOptions.builder()
                .appId("myapp")
                .userIdMechanism(new MacAddressUserId())
                .build();

        return new AppIdAuthentication(options, restOperations());
    }

    // …
}
```

从命令行生成 MAC 地址 userid 的相应命令是：

```
$ echo -n 0AFEDE1234AC | sha256sum
```

|   |MAC 地址是大写的，不带冒号。<br/>包括`echo`的换行将导致不同的散列值<br/>，因此请确保包含`-n`标志。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### 15.3.1.自定义用户 ID

更高级的方法允许你实现自己的`AppIdUserIdMechanism`。这个类必须位于你的 Classpath 上，并且必须实现`org.springframework.vault.authentication.AppIdUserIdMechanism`接口和`createUserId`方法。 Spring Vault 将在每次使用 APPID 进行身份验证以获得令牌时通过调用来获得用户 ID。

MyuseridMechanism.java

```
public class MyUserIdMechanism implements AppIdUserIdMechanism {

  @Override
  public String createUserId() {

    String userId = …
    return userId;
  }
}
```

另见：[Vault 文档：使用应用程序 ID Auth 后台](https://www.vaultproject.io/docs/auth/app-id.html)

### 15.4.Approle 身份验证

[AppRole](https://www.vaultproject.io/docs/auth/app-id.html)允许机器身份验证，就像不推荐的（自 Vault0.6.1）[APPID 身份验证](#vault.authentication.appid)一样。Approle 身份验证由两个难以猜测的（秘密）令牌组成：ROLEID 和 SECTROTID。

Spring Vault 通过仅提供 ROLEID 或与提供的 secretID 一起提供认证支持，并从 Vault 获取 ROLEID/secretID（具有响应展开的推拉模式）。

```
@Configuration
class AppConfig extends AbstractVaultConfiguration {

    // …

    @Override
    public ClientAuthentication clientAuthentication() {

        AppRoleAuthenticationOptions options = AppRoleAuthenticationOptions.builder()
                .roleId(RoleId.provided("…"))
                .secretId(SecretId.wrapped(VaultToken.of("…")))
                .build();

        return new AppRoleAuthentication(options, restOperations());
    }

    // …
}
```

Spring Vault 还支持全拉模式：如果没有提供 Roleid 和 Secretid， Spring Vault 将使用角色名和初始令牌来检索它们。初始令牌可以与 TTL 和使用限制相关联。

```
@Configuration
class AppConfig extends AbstractVaultConfiguration {

    // …

    @Override
    public ClientAuthentication clientAuthentication() {

        VaultToken initialToken = VaultToken.of("…");
        AppRoleAuthenticationOptions options = AppRoleAuthenticationOptions.builder()
                .appRole("…")
                .roleId(RoleId.pull(initialToken))
                .secretId(SecretId.pull(initialToken))
                .build();

        return new AppRoleAuthentication(options, restOperations());
    }

    // …
}
```

另见：[Vault 文档：使用 Approle Auth 后端](https://www.vaultproject.io/docs/auth/approle.html)

### 15.5.AWS-EC2 身份验证

[aws-ec2](https://www.vaultproject.io/docs/auth/aws-ec2.html)Auth 后端为 AWS EC2 实例提供了一种安全的引入机制，允许自动检索保险库令牌。与大多数 Vault 身份验证后端不同，该后端不需要首次部署或提供安全敏感的凭据（令牌、用户名/密码、客户端证书等）。相反，它将 AWS 视为受信任的第三方，并使用以密码签名的动态元数据信息来唯一地表示每个 EC2 实例。

```
@Configuration
class AppConfig extends AbstractVaultConfiguration {

    // …

    @Override
    public ClientAuthentication clientAuthentication() {
        return new AwsEc2Authentication(restOperations());
    }

    // …
}
```

在默认情况下，AWS-EC2 身份验证使 Nonce 能够遵循信任第一次使用（Tofu）原则。任何意外获得 PKCS#7 身份元数据访问权限的一方都可以对 Vault 进行身份验证。

在第一次登录期间， Spring Vault 生成一个 Nonce，该 Nonce 存储在实例 ID 旁边的 auth 后端中。重新验证需要发送相同的 nonce。其他任何一方都没有 Nonce，可以在 Vault 中发出警报，以进行进一步的调查。

nonce 保存在内存中，并在应用程序重新启动时丢失。

AWS-EC2 身份验证角色是可选的，并且是 AMI 的默认值。可以通过在`AwsEc2AuthenticationOptions`中设置身份验证角色来配置身份验证角色。

另见：[Vault 文档：使用 AWS-EC2Auth 后端](https://www.vaultproject.io/docs/auth/aws-ec2.html)

### 15.6.AWS-IAM 身份验证

[aws](https://www.vaultproject.io/docs/auth/aws.html)Auth 后台允许使用现有的 AWS IAM 凭据进行 Vault 登录。

AWS IAM 身份验证创建一个已签名的 HTTP 请求，该请求由 Vault 执行，以使用 AWS STS方法获得签名者的身份。AWSV4 签名需要 IAM 凭据。

IAM 凭据可以从运行时环境获得，也可以从外部提供。具有分配的 IAM 主体的 AWS-EC2、Lambda 和 ECS 等运行时环境不需要特定于客户机的凭据配置，但可以从其元数据源获得这些凭据。

```
@Configuration
class AppConfig extends AbstractVaultConfiguration {

    // …

    @Override
    public ClientAuthentication clientAuthentication() {

        AwsIamAuthenticationOptions options = AwsIamAuthenticationOptions.builder()
                .credentials(new BasicAWSCredentials(…)).build();

        return new AwsIamAuthentication(options, restOperations());
    }

    // …
}
```

例 27.使用 AWS-EC2 实例配置文件作为凭证源

```
@Configuration
class AppConfig extends AbstractVaultConfiguration {

    // …

    @Override
    public ClientAuthentication clientAuthentication() {

        AwsIamAuthenticationOptions options = AwsIamAuthenticationOptions.builder()
                .credentialsProvider(InstanceProfileCredentialsProvider.getInstance()).build();

        return new AwsIamAuthentication(options, restOperations());
    }

    // …
}
```

`AwsIamAuthentication`需要 AWS Java SDK 依赖项（`com.amazonaws:aws-java-sdk-core`），因为身份验证实现使用 AWS SDK 类型作为凭据和请求签名。

你可以通过`AwsIamAuthenticationOptions`配置身份验证。

另见：

* [Vault 文档：使用 AWS Auth 后端](https://www.vaultproject.io/docs/auth/aws.html)

* [AWS 文档：STS GetCallerIdentity](https://docs.aws.amazon.com/STS/latest/APIReference/API_GetCallerIdentity.html)

### 15.7.Azure（MSI）认证

[azure](https://www.vaultproject.io/docs/auth/azure.html)Auth 后端为 Azure VM 实例提供了一种安全的引入机制，允许自动检索 Vault 令牌。与大多数 Vault 身份验证后端不同，该后端不需要首次部署或提供安全敏感的凭据（令牌、用户名/密码、客户端证书等）。相反，它将 Azure 视为受信任的第三方，并使用可绑定到 VM 实例的托管服务标识和实例元数据信息。

```
@Configuration
class AppConfig extends AbstractVaultConfiguration {

    // …

    @Override
    public ClientAuthentication clientAuthentication() {

        AzureMsiAuthenticationOptions options = AzureMsiAuthenticationOptions.builder()
                    .role(…).build();

        return new AzureMsiAuthentication(options, restOperations());
    }

    // …
}
```

Azure 身份验证需要有关 VM 环境的详细信息（订阅 ID、资源组名称、VM 名称）。这些细节可以通过`AzureMsiAuthenticationOptionsBuilder`进行配置。如果不进行配置，`AzureMsiAuthentication`将查询 Azure 的实例元数据服务，以获取这些详细信息。

另见：

* [Vault 文档：使用 Azure Auth 后台](https://www.vaultproject.io/docs/auth/azure.html)

* [Azure 文档：托管服务标识](https://docs.microsoft.com/en-us/azure/active-directory/managed-service-identity/overview)

### 15.8.GCP-GCE 认证

[gcp](https://www.vaultproject.io/docs/auth/gcp.html)Auth 后端允许 Vault 通过使用现有的 GCP（Google Cloud Platform）IAM 和 GCE 凭据登录。

GCPGCE（Google 计算引擎）身份验证为服务帐户创建 JSON Web 令牌形式的签名。使用[实例标识](https://cloud.google.com/compute/docs/instances/verifying-instance-identity)从 GCE 元数据服务获得计算引擎实例的 JWT。该 API 创建了一个 JSON Web 令牌，该令牌可用于确认实例标识。

与大多数 Vault 身份验证后端不同，该后端不需要首次部署或提供安全敏感的凭据（令牌、用户名/密码、客户端证书等）。相反，它将 GCP 视为受信任的第三方，并使用加密签名的动态元数据信息，该信息唯一地表示每个 GCP 服务帐户。

你可以通过`GcpComputeAuthenticationOptions`配置身份验证。

```
@Configuration
class AppConfig extends AbstractVaultConfiguration {

    // …

    @Override
    public ClientAuthentication clientAuthentication() {

        GcpComputeAuthenticationOptions options = GcpComputeAuthenticationOptions.builder()
				.role(…).build();

		GcpComputeAuthentication authentication = new GcpComputeAuthentication(options,
				restOperations());
    }

    // …
}
```

另见：

* [Vault 文档：使用 GCPAuth 后端](https://www.vaultproject.io/docs/auth/gcp.html)

* [GCP 文件：验证实例的身份](https://cloud.google.com/compute/docs/instances/verifying-instance-identity)

### 15.9.GCP-IAM 认证

[gcp](https://www.vaultproject.io/docs/auth/gcp.html)Auth 后端允许 Vault 通过使用现有的 GCP（Google Cloud Platform）IAM 和 GCE 凭据登录。

GCP，IAM 身份验证以 JSON Web 令牌的形式为服务帐户创建签名。通过调用 GCPIAM 的[`projects.serviceAccounts.signJwt`]（https://cloud.google.com/iam/reference/rest/v1/projects.serviceaccounts/signjwt）API，可以获得服务帐户的 JWT。调用者针对 GCPIAM 进行身份验证，并由此证明其身份。此保险库后端将 GCP 视为受信任的第三方。

IAM 凭据可以从运行时环境获得，也可以从外部提供，例如 JSON。JSON 是首选的表单，因为它带有调用`projects.serviceAccounts.signJwt`所需的项目 ID 和服务帐户标识符。

```
@Configuration
class AppConfig extends AbstractVaultConfiguration {

    // …

    @Override
    public ClientAuthentication clientAuthentication() {

        GcpIamAuthenticationOptions options = GcpIamAuthenticationOptions.builder()
				.role(…).credential(GoogleCredentials.getApplicationDefault()).build();

		GcpIamAuthentication authentication = new GcpIamAuthentication(options,
				restOperations());
    }

    // …
}
```

`GcpIamAuthenticationOptions`需要 Google Cloud Java SDK 依赖项（`com.google.apis:google-api-services-iam`和`com.google.auth:google-auth-library-oauth2-http`），因为身份验证实现使用 Google API 进行凭据和 JWT 签名。

你可以通过`GcpIamAuthenticationOptions`配置身份验证。

|   |Google 凭据需要一个 OAuth2 令牌来维护令牌的生命周期。所有 API<br/>都是同步的，因此，`GcpIamAuthentication`不支持`AuthenticationSteps`这是<br/>需要的反应性使用。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

另见：

* [Vault 文档：使用 GCPAuth 后端](https://www.vaultproject.io/docs/auth/gcp.html)

* [GCP 文档：projects.serviceaccounts.signjwt](https://cloud.google.com/iam/reference/rest/v1/projects.serviceAccounts/signJwt)[]()

### 15.10.PCF 认证

[pcf](https://www.vaultproject.io/docs/auth/pcf.html)Auth 后端允许对 PCF 实例进行 Vault 登录。它利用[PCF 的应用程序和容器身份保证](https://content.pivotal.io/blog/new-in-pcf-2-1-app-container-identity-assurance-via-automatic-cert-rotation)。

PCF 身份验证使用实例密钥和证书来创建由 Vault 验证的签名。如果签名匹配，并且可能绑定的组织/空间/应用程序 ID 匹配，Vault 将发出一个范围适当的令牌。

实例凭据可从`CF_INSTANCE_CERT`和`CF_INSTANCE_KEY`变量的文件中获得。

```
@Configuration
class AppConfig extends AbstractVaultConfiguration {

    // …

    @Override
    public ClientAuthentication clientAuthentication() {

        PcfAuthenticationOptions options = PcfAuthenticationOptions.builder()
                .role(…).build();

        PcfAuthentication authentication = new PcfAuthentication(options,
                restOperations());
    }

    // …
}
```

`PcfAuthenticationOptions`需要[Bouncycastle](https://www.bouncycastle.org/latest_releases.html)库来创建 RSA-PSS 签名。

你可以通过`PcfAuthenticationOptions`配置身份验证。

另见：

* [Vault 文档：使用 PCF Auth 后端](https://www.vaultproject.io/docs/auth/pcf.html)

### 15.11.TLS 证书认证

`cert`Auth 后端允许使用 SSL/TLS 客户机证书进行身份验证，这些证书由 CA 签名或自签名。

要启用`cert`身份验证，你需要：

1. 使用 SSL，参见[Vault 客户端 SSL 配置](#vault.client-ssl)

2. 配置包含客户端证书和私钥的 Java`Keystore`

```
@Configuration
class AppConfig extends AbstractVaultConfiguration {

    // …

    @Override
    public ClientAuthentication clientAuthentication() {

        ClientCertificateAuthenticationOptions options = ClientCertificateAuthenticationOptions.builder()
                .path(…).build();

        return new ClientCertificateAuthentication(options, restOperations());
    }

    // …
}
```

另见：[Vault 文档：使用 CERTAuth 后端](https://www.vaultproject.io/docs/auth/cert.html)

### 15.12.空穴身份验证

Cubbyhole 身份验证使用 Vault 原语提供安全的身份验证工作流。Cubbyhole 身份验证使用令牌作为主要登录方法。一个短暂的令牌用于从 Vault 的 Cubbyhole 秘密后端获得第二个登录 VaultToken。登录令牌通常寿命更长，并用于与 Vault 交互。可以从包装的响应或`data`部分检索登录令牌。

**创建一个包装好的令牌**

|   |令牌创建的响应包装需要 Vault0.6.0 或更高版本。|
|---|--------------------------------------------------------------------|

例 28.排版和存储令牌

```
$ vault token-create -wrap-ttl="10m"
Key                            Value
---                            -----
wrapping_token:                397ccb93-ff6c-b17b-9389-380b01ca2645
wrapping_token_ttl:            0h10m0s
wrapping_token_creation_time:  2016-09-18 20:29:48.652957077 +0200 CEST
wrapped_accessor:              46b6aebb-187f-932a-26d7-4f3d86a68319
```

例 29.包装的令牌响应用法

```
@Configuration
class AppConfig extends AbstractVaultConfiguration {

    // …

    @Override
    public ClientAuthentication clientAuthentication() {

        CubbyholeAuthenticationOptions options = CubbyholeAuthenticationOptions
                .builder()
                .initialToken(VaultToken.of("…"))
                .wrapped()
                .build();

        return new CubbyholeAuthentication(options, restOperations());
    }

    // …
}
```

**使用存储令牌**

例 30.排版和存储令牌

```
$ vault token create
Key                    Value
---                    -----
token                  f9e30681-d46a-cdaf-aaa0-2ae0a9ad0819
token_accessor         4eee9bd9-81bb-06d6-af01-723c54a72148
token_duration         0s
token_renewable        false
token_policies         [root]

$ vault token create -use-limit=2 -orphan -no-default-policy -policy=none
Key                    Value
---                    -----
token                  895cb88b-aef4-0e33-ba65-d50007290780
token_accessor         e84b661c-8aa8-2286-b788-f258f30c8325
token_duration         0s
token_renewable        false
token_policies         [none]

$ export VAULT_TOKEN=895cb88b-aef4-0e33-ba65-d50007290780
$ vault write cubbyhole/token token=f9e30681-d46a-cdaf-aaa0-2ae0a9ad0819
```

例 31.存储令牌响应用法

```
@Configuration
class AppConfig extends AbstractVaultConfiguration {

    // …

    @Override
    public ClientAuthentication clientAuthentication() {

        CubbyholeAuthenticationOptions options = CubbyholeAuthenticationOptions
                .builder()
                .initialToken(VaultToken.of("…"))
                .path("cubbyhole/token")
                .build();

        return new CubbyholeAuthentication(options, restOperations());
    }

    // …
}
```

**剩余 TTL/可再生性**

在创建令牌时，从与非零 TTL 相关联的 Cubbyhole 检索的令牌开始其 TTL。这个时间不一定与应用程序启动相同。为了补偿初始延迟，Cubbyhole 身份验证对与非零 TTL 相关的令牌执行自查找，以检索剩余的 TTL。在没有 TTL 的情况下，Cubbyhole 身份验证将不会自我查找包装的令牌，因为零 TTL 表示没有关联的 TTL。

非包装令牌不提供有关可更新性和 TTL 的详细信息，只检索令牌。自我查找将查找可再生性和剩余的 TTL。

另见：

* [保险库文档：令牌](https://www.vaultproject.io/docs/concepts/tokens.html)

* [Vault 文档：Cubbyhole 秘密后端](https://www.vaultproject.io/docs/secrets/cubbyhole/index.html)

* [保险库文档：响应包装](https://www.vaultproject.io/docs/concepts/response-wrapping.html)

### 15.13.Kubernetes 认证

Vault 支持使用 Kubernetes 令牌的基于 0.8.3[kubernetes](https://www.vaultproject.io/docs/auth/kubernetes.html)的身份验证。

使用 Kubernetes 身份验证需要一个 Kubernetes 服务帐户令牌，通常挂载在`/var/run/secrets/kubernetes.io/serviceaccount/token`。该文件包含被读取并发送到 Vault 的令牌。Vault 在登录时使用 Kubernetes 的 API 验证其有效性。

配置 Kubernetes 身份验证至少需要提供角色名：

```
@Configuration
class AppConfig extends AbstractVaultConfiguration {

    // …

    @Override
    public ClientAuthentication clientAuthentication() {

        KubernetesAuthenticationOptions options = KubernetesAuthenticationOptions.builder()
                .role(…).jwtSupplier(…).build();

        return new KubernetesAuthentication(options, restOperations());
    }

    // …
}
```

你可以通过`KubernetesAuthenticationOptions`配置身份验证。

另见：

* [Vault 文档：使用 Kubernetes Auth 后台](https://www.vaultproject.io/docs/auth/kubernetes.html)

* [Kubernetes 文档：为 PODS 配置服务帐户](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/)

### 15.14.认证步骤

`ClientAuthentication`对象描述身份验证流程并执行实际的身份验证步骤。预先组合的身份验证很容易使用，并通过与同步执行的紧密绑定进行配置。

身份验证方法的组合和重用常见步骤，例如将登录有效负载发布到 Vault 或从 HTTP 源检索身份验证输入，并不打算使用`ClientAuthentication`对象。

身份验证步骤提供了公共身份验证活动的可重用性。通过`AuthenticationSteps`创建的步骤以功能风格描述了一个身份验证流程，将实际的身份验证执行留给了特定的执行者。

例 32.存储令牌验证流.

```
AuthenticationSteps.just(VaultToken.of(…));                              (1)
```

|**1**|仅从`VaultToken`创建`AuthenticationSteps`。|
|-----|-------------------------------------------------------|

可以从单个输入创建单步身份验证流。声明多个身份验证步骤的流以`Supplier`或`HttpRequest`开始，这些流提供了一个身份验证状态对象，可用于将其映射或发布到 Vault 以进行登录。

例 33.Approle 认证流程

```
AuthenticationSteps.fromSupplier(                                       (1)

    () -> getAppRoleLogin(options.getRoleId(), options.getSecretId()))  (2)

    .login("auth/{mount}/login", options.getPath());                    (3)
```

|**1**|开始声明`AuthenticationSteps`接受`Supplier<T>`。<br/>状态对象类型取决于`Supplier`响应类型，该响应类型可以在以后的步骤中映射。|
|-----|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|**2**|实际的`Supplier`实现。<br/>在这种情况下创建`Map`。|
|**3**|通过将状态对象（`Map`）发布到 Vault 端点来执行 Vault 登录，以创建 Vault 令牌。<br/>注意，模板变量受 URL 转义的影响。|

身份验证流需要一个执行器来执行实际的登录。我们为不同的执行模型提供了两个执行器：

* `AuthenticationStepsExecutor`作为同步`ClientAuthentication`的插入替换。

* `AuthenticationStepsOperator`用于反应式执行。

许多`ClientAuthentication`都带有静态工厂方法，可以为它们的身份验证特定选项创建`AuthenticationSteps`:

例 34.同步`AuthenticationSteps`执行

```
CubbyholeAuthenticationOptions options = …
RestOperations restOperations = …

AuthenticationSteps steps = CubbyholeAuthentication.createAuthenticationSteps(options);

AuthenticationStepsExecutor executor = new AuthenticationStepsExecutor(steps, restOperations);

VaultToken token = executor.login();
```

### 15.15.令牌生命周期

Vault 的令牌可以与生存时间相关联。通过身份验证方法获得的令牌旨在在会话处于活动状态时使用，并且在应用程序处于活动状态时不应过期。

Spring Vault 提供了[`LifecycleAwareSessionManager`]（https://DOCS. Spring.io/ Spring-vault/DOCS/2.3.1/api/org/springframework/vault/authentication/lifecycleawaresessionmanager.html）会话管理器，它可以更新令牌，直到它到达其终端 TTL，然后执行另一个登录，以获得与会话相关联的下一个令牌。

根据身份验证方法的不同，登录可以创建两种令牌：

* [`VaultToken`]（https://DOCS. Spring.io/ Spring-vault/DOCS/2.3.1/api/org/springframework/vault/support/vaulttoken.html）：封装实际令牌的通用令牌。

* [`LoginToken`]（https://DOCS. Spring.io/ Spring-vault/DOCS/2.3.1/api/org/springframework/vault/support/logintoken.html）：与可再生性/ttl 相关联的令牌。

诸如[`TokenAuthentication`]（https://DOCS. Spring.io/ Spring-vault/DOCS/2.3.1/api/org/springframework/vault/authentication/tokenauthentication.html）之类的认证方法只需创建一个`VaultToken`，其中不包含任何可再生性/TTL 详细信息。`LifecycleAwareSessionManager`将在令牌上运行自我查找，以从 Vault 检索可更新性和 TTL。如果启用了自我查找，`VaultToken`将定期更新。注意，`VaultToken`永远不会被撤销，只有`LoginToken`才会被撤销。

直接创建`LoginToken`的身份验证方法（所有基于登录的身份验证方法）已经为设置令牌更新提供了所有必要的详细信息。如果会话Manager 被关闭，则`LifecycleAwareSessionManager`将撤销从登录中获得的令牌。

## 16.杂项

在本章中学习一些值得一提的细节，比如 Spring 安全集成。

### 16.1. Spring 安全

Spring Vault 通过为[`BytesKeyGenerator`]（https://DOCS. Spring.io/ Spring-security/site/DOCS/current/reference/htmlsingle/# Spring-security-crypto-keygenerators）和[<<`BytesEncryptor`]（https://DOCS. Spring.io/ Spring-security/site/DOCS/current/reference/htmlsingle/# Spring-security-crypto-crypto-cryp 这两种实现都使用 Vault 的`transit`后端。

例 35.`VaultBytesKeyGenerator`示例

```
VaultOperations operations = …;
VaultBytesKeyGenerator generator = new VaultBytesKeyGenerator(operations);

byte[] key = generator.generateKey();
```

例 36.`VaultBytesEncryptor`示例

```
VaultTransitOperations transit = …;

VaultBytesEncryptor encryptor = new VaultBytesEncryptor(transit, "my-key-name");

byte[] ciphertext = encryptor.encrypt(plaintext);

byte[] result = encryptor.decrypt(ciphertext);
```

Vault 封装了一个熵源，该熵源与服务器端密钥管理一起与你的 JVM 分离。这减轻了应用程序开发人员进行适当加密/解密的负担，并将负担推给了 Vault 的运营商。Vault 的操作人员通常包括组织中的安全团队，这意味着他们可以确保数据被正确地加密/解密。此外，由于加密/解密操作必须进入审计日志，因此任何解密事件都会被记录。

后端还支持键旋转，这允许生成指定键的新版本。所有使用该密钥加密的数据都将使用该密钥的最新版本；以前加密的数据可以使用该密钥的旧版本进行解密。管理员可以控制可用于解密的密钥的先前版本，以防止攻击者获得旧的密文副本以成功解密该密钥。

毕竟，Vault 是一种网络服务，每一次操作都会有一个延迟。大量使用加密或随机字节生成的组件可能会在吞吐量和性能方面遇到差异。