Vault 存储库
使用 VaultTemplate 以及映射到 Java 类的响应,可以执行基本的 CRUD 操作,如读取、写入和删除。Vault 仓库在 Vault 之上应用 Spring Data 的仓库概念。Vault 仓库暴露了基本的 CRUD 功能,并支持通过约束标识符属性的谓词、分页和排序进行查询派生。Vault 仓库使用键/值秘密引擎功能来持久化和查询数据。从 2.4 版本开始,Spring Vault 还可以使用键/值版本 2 秘密引擎,实际的秘密引擎版本在运行时发现。
版本化键/值秘密引擎中的删除操作使用 DELETE 操作。秘密不会通过 CrudRepository.delete(…) 销毁。 |
Vault 仓库通过 Vault 的 sys/internal/ui/mounts/… 端点确定挂载路径。请确保您的策略允许访问该路径,否则您将无法使用仓库抽象。 |
| 请查阅 Spring Data Commons 参考文档,了解更多关于 Spring Data 仓库的信息。该参考文档将向您介绍 Spring Data 仓库。 |
使用
要访问存储在 Vault 中的域实体,您可以利用仓库支持,这可以显著简化实现。
@Secret
class Credentials {
@Id String id;
String password;
String socialSecurityNumber;
Address address;
}
我们这里有一个非常简单的域对象。请注意,它有一个名为 id 的属性,用 org.springframework.data.annotation.Id 注解,并且其类型上有一个 @Secret 注解。这两个负责创建用于将对象作为 JSON 持久化到 Vault 中的实际键。
用 @Id 注解的属性以及命名为 id 的属性都被视为标识符属性。带注解的属性优先于其他属性。 |
下一步是声明一个使用域对象的仓库接口。
Credentials 实体的基本仓库接口interface CredentialsRepository extends CrudRepository<Credentials, String> {
}
由于我们的仓库扩展了 CrudRepository,它提供了基本的 CRUD 和查询方法。Vault 仓库需要 Spring Data 组件。请确保在您的类路径中包含 spring-data-commons 和 spring-data-keyvalue 工件。
最简单的方法是设置依赖管理并将工件添加到您的 pom.xml
然后将以下内容添加到 pom.xml 依赖项部分。
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-bom</artifactId>
<version>2025.1.0</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>4.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-keyvalue</artifactId>
<!-- Version inherited from the BOM -->
</dependency>
</dependencies>
我们需要连接一切的中间部分是相应的 Spring 配置。
@Configuration
@EnableVaultRepositories
class ApplicationConfig {
@Bean
VaultTemplate vaultTemplate() {
return new VaultTemplate(…);
}
}
在上述设置下,我们可以继续将 CredentialsRepository 注入到我们的组件中。
@Autowired CredentialsRepository repo;
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 的属性存储在 Vault 哈希中,键模式为 keyspace/id,在本例中为 credentials/heisenberg,存储在键值秘密引擎中。 |
| 2 | 使用提供的 id 检索存储在 keyspace/id 处的对象。 |
| 3 | 计算 Credentials 上 @Secret 定义的键空间 credentials 中可用实体的总数。 |
| 4 | 从 Vault 中移除给定对象的键。 |
对象到 Vault JSON 映射
Vault 仓库使用 JSON 作为交换格式将对象存储在 Vault 中。JSON 和实体之间的对象映射由 VaultConverter 完成。转换器读取和写入包含 VaultResponse 主体的 SecretDocument。VaultResponse 从 Vault 读取,主体由 Jackson 反序列化为 String 和 Object 的 Map。默认的 VaultConverter 实现读取包含嵌套值、List 和 Map 对象的 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。 |
| 类型 | 示例 | 映射值 |
|---|---|---|
简单类型 |
String firstname = "Walter"; |
"firstname": "Walter" |
复杂类型 |
Address address = new Address("308 Negra Arroyo Lane"); |
"address": { "street": "308 Negra Arroyo Lane" } |
List |
List<String> nicknames = asList("walt", "heisenberg"); |
"nicknames": ["walt", "heisenberg"] |
Map |
Map<String, Integer> atts = asMap("age", 51) |
"atts" : {"age" : 51} |
List |
List<Address> addresses = asList(new Address("308… |
"address": [{ "street": "308 Negra Arroyo Lane" }, …] |
您可以通过在 VaultCustomConversions 中注册 Converter 来自定义映射行为。这些转换器可以负责将类型(如 LocalDate)转换为/从 SecretDocument,其中第一个适用于转换简单属性,最后一个适用于将复杂类型转换为其 JSON 表示。第二个选项提供对生成的 SecretDocument 的完全控制。将对象写入 Vault 将删除内容并重新创建整个条目,因此未映射的数据将丢失。
查询和查询方法
查询方法允许从方法名称自动派生简单查询。Vault 没有查询引擎,但需要直接访问 HTTP 上下文路径。Vault 查询方法将 Vault 的 API 可能性转换为查询。查询方法执行会列出上下文路径下的子项,对 Id 应用过滤,可选地使用 offset/limit 限制 Id 流,并在获取结果后应用排序。
interface CredentialsRepository extends CrudRepository<Credentials, String> {
List<Credentials> findByIdStartsWith(String prefix);
}
Vault 仓库的查询方法只支持对 @Id 属性的谓词进行查询。 |
以下是 Vault 支持的关键字概述。
| 关键字 | 示例 |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
排序和分页
查询方法通过在内存中选择从 Vault 上下文路径检索到的 Id 子列表(偏移/限制)来支持排序和分页。排序不受特定字段的限制,这与查询方法谓词不同。未分页排序在 Id 过滤后应用,并且所有结果秘密都从 Vault 中获取。这样,查询方法只获取作为结果一部分返回的结果。
使用分页和排序需要在过滤 Id 之前获取秘密,这会影响性能。排序和分页保证返回相同的结果,即使 Vault 返回的 Id 的自然顺序发生变化。因此,首先从 Vault 中获取所有 Id,然后应用排序,之后再进行过滤和偏移/限制。
interface CredentialsRepository extends PagingAndSortingRepository<Credentials, String> {
List<Credentials> findTop10ByIdStartsWithOrderBySocialSecurityNumberDesc(String prefix);
List<Credentials> findByIdStarts(String prefix, Pageable pageRequest);
}
乐观锁
Vault 的键/值秘密引擎版本 2 可以维护版本化的秘密。Spring Vault 通过域模型中带有 @Version 注解的版本属性支持版本控制。使用乐观锁可以确保更新仅应用于具有匹配版本的秘密。因此,版本属性的实际值通过 cas 属性添加到更新请求中。如果在此期间其他操作更改了秘密,则会抛出 OptimisticLockingFailureException 并且秘密不会更新。
版本属性必须是数字属性,例如 int 或 long,并在更新秘密时映射到 cas 属性。
@Secret
class VersionedCredentials {
@Id String id;
@Version int version;
String password;
String socialSecurityNumber;
Address address;
}
以下示例展示了这些功能
VersionedCredentialsRepository repo = …;
VersionedCredentials credentials = repo.findById("sample-credentials").get(); (1)
VersionedCredentials concurrent = repo.findById("sample-credentials").get(); (2)
credentials.setPassword("something-else");
repos.save(credentials); (3)
concurrent.setPassword("concurrent change");
repos.save(concurrent); // throws OptimisticLockingFailureException (4)
| 1 | 通过其 Id sample-credentials 获取一个秘密。 |
| 2 | 通过其 Id sample-credentials 获取秘密的第二个实例。 |
| 3 | 更新秘密并让 Vault 递增版本。 |
| 4 | 更新使用先前版本的第二个实例。操作因 OptimisticLockingFailureException 而失败,因为 Vault 中的版本在此期间已递增。 |
| 删除版本化秘密时,按 Id 删除会删除最新的秘密。按实体删除会在提供的版本处删除秘密。 |
访问版本化秘密
键/值版本 2 秘密引擎维护秘密的版本,可以通过在 Vault 仓库接口声明中实现 RevisionRepository 来访问这些版本。修订仓库定义查找方法以获取特定标识符的修订。标识符必须是 String。
RevisionRepositoryinterface RevisionCredentialsRepository extends CrudRepository<Credentials, String>,
RevisionRepository<Credentials, String, Integer> (1)
{
}
| 1 | 第一个类型参数 (Credentials) 表示实体类型,第二个 (String) 表示 id 属性的类型,最后一个 (Integer) 是修订号的类型。Vault 只支持 String 标识符和 Integer 修订号。 |
用法
您现在可以使用 RevisionRepository 中的方法来查询实体的修订版,如下例所示
RevisionRepositoryRevisionCredentialsRepository repo = …;
Revisions<Integer, Credentials> revisions = repo.findRevisions("my-secret-id");
Page<Revision<Integer, Credentials>> firstPageOfRevisions = repo.findRevisions("my-secret-id", Pageable.ofSize(4));