跳到主要内容

保险库仓库

DeepSeek V3 中英对照 Vault Repositories

使用 VaultTemplate 以及将响应映射到 Java 类的方式,可以实现基本的数据操作,如读取、写入和删除。Vault 存储库在 Vault 的基础上应用了 Spring Data 的存储库概念。Vault 存储库暴露了基本的 CRUD 功能,并支持通过谓词约束标识符属性、分页和排序来派生查询。Vault 存储库使用键/值(key/value)机密引擎功能来持久化和查询数据。从 2.4 版本开始,Spring Vault 还可以使用键/值版本 2 机密引擎,实际的机密引擎版本在运行时被检测到。

备注

在版本化的键/值存储引擎中,删除操作使用 DELETE 操作。通过 CrudRepository.delete(…) 不会销毁密钥。

备注

有关 Spring Data 仓库的更多信息,请参阅 Spring Data Commons 参考文档。参考文档将为你介绍 Spring Data 仓库。

用法

要访问存储在 Vault 中的领域实体,你可以利用仓库支持,这大大简化了实现这些实体的过程。

示例 1. 凭证实体示例

@Secret
class Credentials {

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

我们这里有一个非常简单的领域对象。请注意,它有一个名为 id 的属性,该属性使用了 org.springframework.data.annotation.Id 注解,并且在其类型上有一个 @Secret 注解。这两个注解负责创建用于将对象作为 JSON 存储在 Vault 中的实际键。

备注

@Id 注解的属性以及名为 id 的属性都被视为标识符属性。带有注解的属性优先于其他属性。

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

示例 2. Credentials 实体的基本 Repository 接口

interface CredentialsRepository extends CrudRepository<Credentials, String> {

}
java

由于我们的仓库扩展了 CrudRepository,它提供了基本的 CRUD 和查询方法。Vault 仓库需要 Spring Data 组件。请确保在你的类路径中包含 spring-data-commonsspring-data-keyvalue 构件。

实现这一点的最简单方法是设置依赖管理,并将构件添加到你的 pom.xml 中:

然后在 pom.xml 的 dependencies 部分添加以下内容。

示例 3. 使用 Spring Data BOM

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-bom</artifactId>
<version>2023.1.9</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>3.1.2</version>
</dependency>

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

</dependencies>
xml

我们需要在中间将各个部分粘合在一起的是相应的 Spring 配置。

示例 4. Vault 仓库的 JavaConfig 配置

@Configuration
@EnableVaultRepositories
class ApplicationConfig {

@Bean
VaultTemplate vaultTemplate() {
return new VaultTemplate();
}
}
java

根据上述设置,我们可以继续将 CredentialsRepository 注入到我们的组件中。

示例 5. 访问 Person 实体

@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
}
java
  • Credentials 的属性存储在 Vault Hash 中,使用键模式 keyspace/id,在本例中为 credentials/heisenberg,存储在键值对秘密引擎中。

  • 使用提供的 id 检索存储在 keyspace/id 处的对象。

  • 计算由 Credentials 上的 @Secret 定义的 credentials 键空间内可用的实体总数。

  • 从 Vault 中移除给定对象的键。

对象到 Vault JSON 映射

Vault 仓库使用 JSON 作为交换格式在 Vault 中存储对象。JSON 和实体之间的对象映射由 VaultConverter 完成。该转换器读取和写入包含来自 VaultResponse 的正文的 SecretDocumentVaultResponse 从 Vault 中读取,并由 Jackson 将正文反序列化为 StringObjectMap。默认的 VaultConverter 实现读取包含嵌套值、ListMap 对象的 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"
}
}
json
  • _class 属性包含在根级别以及任何嵌套的接口或抽象类型中。

  • 简单属性值通过路径映射。

  • 复杂类型的属性被映射为嵌套对象。

备注

@Id 属性必须映射为 String 类型。

表 1. 默认映射规则

类型示例映射值
简单类型
(例如 String)
String firstname = "Walter";"firstname": "Walter"
复杂类型
(例如 Address)
Address address = new Address("308 Negra Arroyo Lane");"address": { "street": "308 Negra Arroyo Lane" }
简单类型的列表List<String> nicknames = asList("walt", "heisenberg");"nicknames": ["walt", "heisenberg"]
简单类型的映射Map<String, Integer> atts = asMap("age", 51)"atts" : {"age" : 51}
复杂类型的列表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 进行过滤,可选地使用偏移量/限制来限制 Id 流,并在获取结果后应用排序。

示例 6. 示例仓库查询方法

interface CredentialsRepository extends CrudRepository<Credentials, String> {

List<Credentials> findByIdStartsWith(String prefix);
}
java
备注

Vault 仓库的查询方法仅支持对 @Id 属性进行谓词的查询。

以下是 Vault 支持的关键字概览。

表 2. 查询方法支持的关键字

关键字示例
After, GreaterThanfindByIdGreaterThan(String id)
GreaterThanEqualfindByIdGreaterThanEqual(String id)
Before, LessThanfindByIdLessThan(String id)
LessThanEqualfindByIdLessThanEqual(String id)
BetweenfindByIdBetween(String from, String to)
InfindByIdIn(Collection ids)
NotInfindByIdNotIn(Collection ids)
Like, StartingWith, EndingWithfindByIdLike(String id)
NotLike, IsNotLikefindByIdNotLike(String id)
ContainingfindByFirstnameContaining(String id)
NotContainingfindByFirstnameNotContaining(String name)
RegexfindByIdRegex(String id)
(无关键字)findById(String name)
NotfindByIdNot(String id)
AndfindByLastnameAndFirstname
OrfindByLastnameOrFirstname
Is,EqualsfindByFirstname,findByFirstnameIs,findByFirstnameEquals
Top,FirstfindFirst10ByFirstname,findTop5ByFirstname

排序与分页

查询方法支持通过在内存中选择从 Vault 上下文路径检索的 ID 子列表(偏移量/限制)来进行排序和分页。与查询方法谓词不同,排序不限于特定字段。未分页的排序在 ID 过滤之后应用,并且所有结果中的密钥都从 Vault 中获取。这样,查询方法仅获取也作为结果的一部分返回的结果。

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

示例 7. 分页和排序仓库

interface CredentialsRepository extends PagingAndSortingRepository<Credentials, String> {

List<Credentials> findTop10ByIdStartsWithOrderBySocialSecurityNumberDesc(String prefix);

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

乐观锁

Vault 的键/值(key/value)秘密引擎版本 2 可以维护版本化的秘密。Spring Vault 通过在域模型中带有 @Version 注解的版本属性来支持版本控制。使用乐观锁定确保更新仅应用于具有匹配版本的秘密。因此,版本属性的实际值通过 cas 属性添加到更新请求中。如果在此期间另一个操作修改了秘密,则会抛出 OptimisticLockingFailureException,并且秘密不会被更新。

版本属性必须是数字属性,例如 intlong,并且在更新密钥时映射到 cas 属性。

示例 8. 带版本的实体示例

@Secret
class VersionedCredentials {

@Id String id;
@Version int version;
String password;
String socialSecurityNumber;
Address address;
}
java

以下示例展示了这些特性:

示例 9. 带版本的实体示例

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>
java
  • 根据其 Id sample-credentials 获取一个 secret。

  • 根据其 Id sample-credentials 获取第二个 secret 实例。

  • 更新 secret 并让 Vault 增加版本号。

  • 更新使用先前版本的第二个实例。由于在此期间 Vault 中的版本号已增加,操作将失败并抛出 OptimisticLockingFailureException

备注

在删除版本化的密钥时,按 Id 删除会删除最新的密钥。按实体删除会删除指定版本的密钥。

访问版本化密钥

Key/Value 版本 2 的 secrets engine 维护了可以通过在 Vault 仓库接口声明中实现 RevisionRepository 来访问的 secrets 版本。Revision 仓库定义了查找方法,以获取特定标识符的修订版本。标识符必须是 String 类型。

示例 10. 实现 RevisionRepository

interface RevisionCredentialsRepository extends CrudRepository<Credentials, String>,
RevisionRepository<Credentials, String, Integer> 1
{

}
java
  • 第一个类型参数 (Credentials) 表示实体类型,第二个 (String) 表示 id 属性的类型,最后一个 (Integer) 是修订号的类型。Vault 仅支持 String 类型的标识符和 Integer 类型的修订号。

用法

现在你可以使用 RevisionRepository 中的方法来查询实体的修订版本,如下例所示:

示例 11. 使用 RevisionRepository

RevisionCredentialsRepository repo =;

Revisions<Integer, Credentials> revisions = repo.findRevisions("my-secret-id");

Page<Revision<Integer, Credentials>> firstPageOfRevisions = repo.findRevisions("my-secret-id", Pageable.ofSize(4));
java