Vault Client 访问
Spring Vault 框架提供的价值或许最好通过下表概述的一系列操作来展示。该表显示了在选择特定客户端选项时,Spring 负责哪些功能:
| 客户端 | HTTP 请求 | 认证 | Vault 操作 | 错误处理 |
|---|---|---|---|---|
| RestClient | 底层 HTTP 请求访问,可完全控制 URI、请求头和消息转换。你需要手动构造请求并处理响应。VaultEndpoint 可以通过 UriBuilderFactory 配置,从而支持相对路径的使用。 | 没有内置的认证支持;你必须自己管理令牌和请求头。 | 没有直接的 Vault 领域操作;你需要手动为 Vault 端点构造请求。 | 4xx/5xx 状态被转换为 HttpServerErrorException 或 HttpClientErrorException,onStatus 允许你注册自定义处理器 |
| VaultClient | 基于 RestClient 构建的 Vault 专用流式 HTTP 客户端。提供常见 HTTP 方法和请求构造的方法,以及 VaultEndpoint 支持,通过相对路径将对功能的访问与实际 Vault 服务器配置解耦。 | 用于设置令牌(token(…))和命名空间(namespace(…))的便捷方法;更易于管理认证请求头,并可与 ClientAuthentication 实现一起使用以登录 Vault。 | 基于路径的请求方法;允许通过 ResponseSpec.body() 和 ResponseSpec.toEntity() 方法将 Vault 响应消费为 VaultResponse;直接支持响应包裹。 | 4xx/5xx 状态被转换为 VaultClientResponseException,onStatus 允许你注册自定义处理器 |
| VaultTemplate | Vault 操作的高级抽象。可以通过客户端回调发起 HTTP 请求。 | 支持处理令牌续期和认证的简单 ClientAuthentication 和 SessionManager 实现。 | 用于多种 Vault 领域操作(读取、写入、删除、列出)的 Java API,支持各种机密后端(KV、Cubbyhole、PKI、Transit 等)。 | 构建在 VaultClient 之上,将 HTTP 错误转换为 Spring Vault 异常(例如 VaultException)。 |
客户端示例
- Java
- Kotlin
RestClient client = RestClient.create();
VaultResponse response = client.get()
.uri("https://vault.example.com/v1/secret/my-secret")
.header("X-Vault-Token", "…")
.header("X-Vault-Namespace", "…")
.retrieve()
.body(VaultResponse.class);
val client = RestClient.create()
val response = client.get()
.uri("https://vault.example.com/v1/secret/my-secret")
.header("X-Vault-Token", "…")
.header("X-Vault-Namespace", "…")
.retrieve()
.body<VaultResponse>()
- Java
- Kotlin
VaultClient client = VaultClient.create(VaultEndpoint.create("vault.example.com", 8200));
VaultResponse response = client.get()
.path("secret/my-secret")
.token(VaultToken.of(…))
.namespace(…)
.retrieve()
.requiredBody();
val client = VaultClient.create(VaultEndpoint.create("vault.example.com", 8200))
val response = client.get()
.path("secret/my-secret")
.token(VaultToken.of(…))
.namespace(…)
.retrieve()
.requiredBody()
- Java
- Kotlin
VaultTemplate vaultOperations = new VaultTemplate(vaultClient, sessionManager);
VaultResponse response = vaultOperations.read("secret/my-secret");
// or
Object value = vaultOperations.readRequired("secret/my-secret").getRequiredData().get("value");
val vaultOperations = VaultTemplate(vaultClient, sessionManager)
val response = vaultOperations.read("secret/my-secret")
// or
val value = vaultOperations.readRequired("secret/my-secret").getRequiredData()["value"]
建议
如果你需要最大的灵活性并完全控制 HTTP 客户端(连接池、JVM 级别的自定义 TLS 密钥库、用于诊断的 wire 日志),请从配置正确的 RestClient 或请求工厂开始。这是框架级代码或必须适应多种环境的库的正确选择。
对于大多数使用 Vault 的应用程序代码,VaultClient 达到了正确的平衡:它提供了可读的流式 API,以及类似 RestClient 外观的简单请求/响应处理。其安全默认值(例如端点限定和令牌辅助方法)避免了常见的陷阱。
当你更倾向于在更高级别的抽象层上使用 Vault 时,请选择 VaultTemplate。它减少了样板代码,并与 Spring 的转换和异常转换模式集成。当你的应用程序执行机密 CRUD 操作并且你想要简洁、意图明确的代码时使用它,它为 Vault 提供了 Java API。
VaultClient 和 ReactiveVaultClient
Spring Vault 为调用 Vault 端点提供了以下选择:
VaultClient— 基于 Spring Framework 的 RestClient 的同步客户端,具有流式 APIReactiveVaultClient— 基于 Spring Framework 的 WebClient 的非阻塞响应式客户端,具有流式 API
VaultClient 是一个同步 HTTP 客户端,提供执行请求的流式 API。它的响应式变体 ReactiveVaultClient 为非阻塞访问镜像了 VaultClient 的设计。从今往后,文档展示的是 VaultClient。除了响应式方面,ReactiveVaultClient 的工作方式相同。它作为 HTTP 库之上的抽象,处理 HTTP 请求和响应内容与更高级别 Java 对象之间的转换,构建在 RestClient 之上(其响应式变体则使用 WebClient)。
创建 VaultClient
VaultClient 具有静态的 create 快捷方法。它还暴露了一个带有更多选项的 builder():
- 选择要使用的 HTTP 库,请参阅 客户端请求工厂
- 设置
VaultEndpoint或VaultEndpointProvider以启用相对路径使用,并将请求限制在配置的 Vault 服务器 - 设置默认请求头
- 自定义底层
RestClient(对于ReactiveVaultClient则是WebClient)
一旦创建,VaultClient 可以在多个线程中安全使用。
使用 Spring Vault 的配置基础架构时,你可以注册 VaultClientCustomizer 或其响应式变体 ReactiveVaultClientCustomizer,以自定义 Spring Vault 创建的 VaultClient 或 ReactiveVaultClient 实例。
以下示例展示了如何创建或构建一个 VaultClient:
- Java
- Kotlin
VaultClient defaultClient = VaultClient.create();
VaultClient customClient = VaultClient.builder()
.endpoint(VaultEndpoint.create("vault.acme.com", 8200))
.requestFactory(new HttpComponentsClientHttpRequestFactory())
.defaultHeader("My-Header", "Foo")
.configureRestClient(builder -> … )
.build();
val defaultClient = VaultClient.create()
val customClient = VaultClient.builder()
.endpoint(VaultEndpoint.create("vault.acme.com", 8200))
.requestFactory(HttpComponentsClientHttpRequestFactory())
.defaultHeader("My-Header", "Foo")
.configureRestClient{ … }
.build()
使用 VaultClient
要执行 Vault 请求,首先指定要使用的 HTTP 方法。使用 get()、post()、delete() 等便捷方法,或 method(HttpMethod)。
请求路径
接下来,使用 path 方法指定请求路径。路径通常指定为 String,带有可选的 URI 模板变量。以下展示了如何执行请求:
- Java
- Kotlin
int id = 42;
vaultClient.get()
.path("secret/{id}", id)
// ...
val id = 42
vaultClient.get()
.path("secret/{id}", id)
// ...
字符串 URL 默认进行编码,但可以通过构建带有自定义 uriBuilderFactory 的客户端来更改。URL 也可以通过函数或 java.net.URI 提供,两者都不会被编码。使用 URI 可以访问外部服务器。
请求头和请求体
如有必要,可以通过使用 header(String, String) 或 token(VaultToken) 等添加请求头来操作 Vault 请求。
请求体本身可以通过 body(Object) 设置,内部使用 HTTP 消息转换。或者,请求体可以使用 ParameterizedTypeReference 设置,允许你使用泛型。
检索响应
请求设置完成后,可以通过在 retrieve() 之后链式调用方法发送。例如,可以使用 retrieve().body(Class) 或 retrieve().body(ParameterizedTypeReference) 访问响应体(用于列表等参数化类型)。便捷方法 retrieve().body() 使用 Vault 的默认响应类型 VaultResponse 返回响应体。body 方法将响应内容转换为各种类型 — 例如,字节可以转换为 String,JSON 可以使用 Jackson 转换为对象。
响应也可以转换为 ResponseEntity,通过 retrieve().toEntity(Class) 同时访问响应头和响应体。
单独调用 retrieve() 是一个无操作,并返回一个 ResponseSpec。应用程序必须在 ResponseSpec 上调用终端操作才能产生任何副作用。如果消费响应对你的用例没有意义,你可以使用 retrieve().toBodilessEntity()。
此示例展示了如何使用 VaultClient 执行简单的 GET 请求。
- Java
- Kotlin
VaultResponse result = vaultClient.get() 1
.path("secret/my-secret") 2
.retrieve() 3
.requiredBody(); 4
System.out.println(result); 5
设置一个 GET 请求
在 Vault 服务器上指定路径
检索响应
将响应转换为
VaultResponse打印结果
val result = vaultClient.get() 1
.path("secret/my-secret") 2
.retrieve() 3
.requiredBody() 4
println(result) 5
设置一个 GET 请求
在 Vault 服务器上指定路径
检索响应
将响应转换为
VaultResponse打印结果
通过 ResponseEntity 提供对响应状态码和请求头的访问:
- Java
- Kotlin
ResponseEntity<VaultResponse> result = vaultClient.get() 1
.path("secret/my-secret") 1
.retrieve()
.toEntity(); 2
System.out.println("Response status: " + result.getStatusCode()); 3
System.out.println("Response headers: " + result.getHeaders()); 3
System.out.println("Contents: " + result.getBody()); 3
为指定的 URL 设置一个 GET 请求
将响应转换为
ResponseEntity打印结果
val result = vaultClient.get() 1
.path("secret/my-secret") 1
.retrieve()
.toEntity<String>() 2
println("Response status: " + result.statusCode) 3
println("Response headers: " + result.headers) 3
println("Contents: " + result.body) 3
为指定的 URL 设置一个 GET 请求
将响应转换为
ResponseEntity打印结果
错误处理
默认情况下,当检索到具有 4xx 或 5xx 状态码的响应时,VaultClient 会抛出 VaultClientResponseException 的子类。此行为可以使用 onStatus 覆盖。
- Java
- Kotlin
String result = vaultClient.get()
.path("secret/this-path-does-not-exist") 1
.retrieve()
.onStatus(HttpStatusCode::is4xxClientError, (request, response) -> { 2
throw new MyCustomRuntimeException(response.getStatusCode(), response.getHeaders()); 3
})
.body(String.class);
为返回 404 状态码的 URL 创建一个 GET 请求
为所有 4xx 状态码设置状态处理器
抛出一个自定义异常
val result = vaultClient.get()
.path("secret/this-path-does-not-exist") 1
.retrieve()
.onStatus(HttpStatusCode::is4xxClientError) { _, response -> 2
throw MyCustomRuntimeException(response.getStatusCode(), response.getHeaders()) } 3
.body<String>()
为返回 404 状态码的 URL 创建一个 GET 请求
为所有 4xx 状态码设置状态处理器
抛出一个自定义异常