Vault 存储库

Working with VaultTemplate and responses mapped to Java classes allows basic data operations like read, write and delete. Vault repositories apply Spring Data’s repository concept on top of Vault. A Vault repository exposes basic CRUD functionality and supports query derivation with predicates constraining the identifier property, paging and sorting. Vault repositories use the key/value secrets engine functionality to persist and query data. As of version 2.4, Spring Vault can use additionally key/value version 2 secrets engine, the actual secrets engine version is discovered during runtime.spring-doc.cadn.net.cn

在版本化的键/值 secrets 引擎中,删除操作使用 DELETE 操作。 Secrets 不会通过 CrudRepository.delete(…) 被销毁。
Vault Repositories 通过 Vault 的 sys/internal/ui/mounts/… 端点确定挂载路径。请确保您的策略允许访问该路径,否则您将无法使用存储库抽象。
关于 Spring Data Repositories 的更多信息,请参阅 Spring Data Commons 参考文档。 参考文档将为您提供 Spring Data 仓库的介绍。

用法

要访问存储在 Vault 中的域实体,您可以利用存储库支持,这将大大简化这些实体的实现。spring-doc.cadn.net.cn

示例 1. 示例凭据实体
@Secret
class Credentials {

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

我们这里有一个非常简单的领域对象。
请注意,它有一个名为 id 的属性,该属性被注解为 org.springframework.data.annotation.Id,并且其类型上有一个 @Secret 注解。
这两个注解负责创建用于将对象作为 JSON 持久化到 Vault 中的实际键。spring-doc.cadn.net.cn

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

下一步是声明一个使用领域对象的存储库接口。spring-doc.cadn.net.cn

Example 2. Basic Repository Interface for Credentials entities
interface CredentialsRepository extends CrudRepository<Credentials, String> {

}

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

实现此目的的最简单方法是设置依赖项管理并将构件添加到您的 pom.xml 中:spring-doc.cadn.net.cn

然后在 pom.xml 依赖部分添加以下内容。spring-doc.cadn.net.cn

示例 3. 使用 Spring Data BOM
<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework.data</groupId>
      <artifactId>spring-data-bom</artifactId>
      <version>2025.1.3</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.1</version>
  </dependency>

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

</dependencies>

将各个部分粘合在一起所需要的正是相应的 Spring 配置。spring-doc.cadn.net.cn

示例 4. Vault 存储库的 JavaConfig 配置
@Configuration
@EnableVaultRepositories
class ApplicationConfig {

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

鉴于上述设置,我们可以继续将 CredentialsRepository 注入到我们的组件中。spring-doc.cadn.net.cn

示例 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)
}
1 Credentials的属性存储在 Vault 哈希中,键模式为keyspace/id,在此例中为credentials/heisenberg,位于键值密钥 secrets 引擎中。
2 使用提供的 id 检索存储在 keyspace/id 处的对象。
3 计算在 Credentials 上由 @Secret 定义的密钥空间 credentials 中可用的实体总数。
4 从 Vault 中移除给定对象的键。

对象到 Vault JSON 映射

Vault 存储库使用 JSON 作为交换格式将对象存储在 Vault 中。 对象与 JSON 之间的映射由 VaultConverter 完成。 转换器读取和写入包含来自 VaultResponse 的正文的 SecretDocument。从 Vault 中读取 VaultResponse,并由 Jackson 将正文反序列化为 StringObject 类型的 Map。 默认的 VaultConverter 实现读取带有嵌套值、ListMap 对象的 Map,并将这些对象转换为实体,反之亦然。spring-doc.cadn.net.cn

鉴于前几节中提到的Credentials类型,默认映射如下所示:spring-doc.cadn.net.cn

{
  "_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
表1. 默认映射规则
类型 示例 映射值

简单类型
(例如,字符串)spring-doc.cadn.net.cn

String firstname = \"Walter\";spring-doc.cadn.net.cn

\"firstname\": \"Walter\"spring-doc.cadn.net.cn

复杂类型
(例如,地址)spring-doc.cadn.net.cn

Address 地址 = new Address(\"308 Negra Arroyo Lane\");spring-doc.cadn.net.cn

\"address\": { \"street\": \"308 Negra Arroyo Lane\" }spring-doc.cadn.net.cn

列表
简单类型spring-doc.cadn.net.cn

List<String> nicknames = asList(\"walt\", \"heisenberg\");spring-doc.cadn.net.cn

\"昵称\": [\"walt\", \"heisenberg\"]spring-doc.cadn.net.cn

映射
简单类型spring-doc.cadn.net.cn

Map<String, Integer> atts = asMap(\"age\", 51)spring-doc.cadn.net.cn

\"atts\" : {\"age\" : 51}spring-doc.cadn.net.cn

列表
复杂类型spring-doc.cadn.net.cn

List<Address> addresses = asList(new Address(\"308…spring-doc.cadn.net.cn

\"address\": [{ \"street\": \"308 Negra Arroyo Lane\" }, …]spring-doc.cadn.net.cn

您可以通过在VaultCustomConversions中注册一个Converter来定制映射行为。 这些转换器可以处理从/到某种类型的转换,例如LocalDate以及SecretDocument, 其中第一个适合用于转换简单属性,而最后一个适合将复杂类型转换为其JSON表示形式。 第二个选项提供了对生成的SecretDocument的完全控制。 将对象写入Vault 会删除内容并重新创建整个条目,因此未映射的数据将会丢失。spring-doc.cadn.net.cn

查询与查询方法

查询方法允许从方法名称自动推导出简单查询。 Vault 没有查询引擎,但需要直接访问 HTTP 上下文路径。 Vault 查询方法将 Vault 的 API 功能转换为查询。 查询方法的执行会列出上下文路径下的子节点,对 Id 应用过滤条件,可选地使用偏移量/限制来限制 Id 流,并在获取结果后应用排序。spring-doc.cadn.net.cn

示例 6. 示例仓库查询方法
interface CredentialsRepository extends CrudRepository<Credentials, String> {

  List<Credentials> findByIdStartsWith(String prefix);
}
Query methods for Vault repositories support only queries with predicates on the @Id property.

以下是 Vault 支持的关键字概述。spring-doc.cadn.net.cn

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

After, GreaterThanspring-doc.cadn.net.cn

findByIdGreaterThan(String id)spring-doc.cadn.net.cn

GreaterThanEqualspring-doc.cadn.net.cn

findByIdGreaterThanEqual(String id)spring-doc.cadn.net.cn

Before, LessThanspring-doc.cadn.net.cn

findByIdLessThan(String id)spring-doc.cadn.net.cn

LessThanEqualspring-doc.cadn.net.cn

findByIdLessThanEqual(String id)spring-doc.cadn.net.cn

Betweenspring-doc.cadn.net.cn

findByIdBetween(String from, String to)spring-doc.cadn.net.cn

Inspring-doc.cadn.net.cn

findByIdIn(Collection ids)spring-doc.cadn.net.cn

NotInspring-doc.cadn.net.cn

findByIdNotIn(Collection ids)spring-doc.cadn.net.cn

Like, StartingWith, EndingWithspring-doc.cadn.net.cn

findByIdLike(String id)spring-doc.cadn.net.cn

NotLike, IsNotLikespring-doc.cadn.net.cn

findByIdNotLike(String id)spring-doc.cadn.net.cn

Containingspring-doc.cadn.net.cn

findByFirstnameContaining(String id)spring-doc.cadn.net.cn

NotContainingspring-doc.cadn.net.cn

findByFirstnameNotContaining(String name)spring-doc.cadn.net.cn

Regexspring-doc.cadn.net.cn

findByIdRegex(String id)spring-doc.cadn.net.cn

(No keyword)spring-doc.cadn.net.cn

findById(String name)spring-doc.cadn.net.cn

Notspring-doc.cadn.net.cn

findByIdNot(String id)spring-doc.cadn.net.cn

Andspring-doc.cadn.net.cn

findByLastnameAndFirstnamespring-doc.cadn.net.cn

Orspring-doc.cadn.net.cn

findByLastnameOrFirstnamespring-doc.cadn.net.cn

Is,Equalsspring-doc.cadn.net.cn

findByFirstname,findByFirstnameIs,findByFirstnameEqualsspring-doc.cadn.net.cn

Top,Firstspring-doc.cadn.net.cn

findFirst10ByFirstname,findTop5ByFirstnamespring-doc.cadn.net.cn

排序与分页

查询方法支持通过从 Vault 上下文路径检索到的 Id 列表中在内存中选择子列表(偏移量/限制)来进行排序和分页。 排序不受特定字段的限制,这与查询方法谓词不同。 未分页的排序会在 Id 过滤之后应用,并且会从 Vault 中获取所有生成的密钥。 通过这种方式,查询方法只会获取作为结果一部分返回的结果。spring-doc.cadn.net.cn

使用分页和排序需要在过滤 Id 之前进行隐秘获取,这会影响性能。 排序和分页确保即使 Vault 返回的 Id 的自然顺序发生变化,结果也保持一致。 因此,所有 Id 首先从 Vault 中获取,然后应用排序,接着进行过滤以及偏移/限制操作。spring-doc.cadn.net.cn

示例 7. 分页和排序存储库
interface CredentialsRepository extends PagingAndSortingRepository<Credentials, String> {

  List<Credentials> findTop10ByIdStartsWithOrderBySocialSecurityNumberDesc(String prefix);

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

乐观锁

Vaults的键/值秘密引擎版本2可以维护带版本的秘密。 Spring Vault通过领域模型中的版本属性支持版本控制,这些属性使用@Version注解。 使用乐观锁确保更新仅应用于具有匹配版本的秘密。 因此,版本属性的实际值会通过cas属性添加到更新请求中。 如果在此期间另一个操作更改了秘密,则会抛出OptimisticLockingFailureException异常,并且秘密不会被更新。spring-doc.cadn.net.cn

版本属性必须是诸如 intlong 之类的数值属性,并在更新密钥时映射到 cas 属性。spring-doc.cadn.net.cn

示例 8. 示例版本化实体
@Secret
class VersionedCredentials {

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

以下示例展示了这些特性:spring-doc.cadn.net.cn

示例 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)
1 通过其 Id sample-credentials 获取密钥。
2 通过其 Id sample-credentials 获取秘密的第二个实例。
3 更新密钥并让 Vault 增加版本。
4 更新使用先前版本的第二个实例。 该操作失败,返回OptimisticLockingFailureException,因为此时 Vault 中的版本已被增加。
当删除带版本的密钥时,按 Id 删除会删除最新的密钥。按实体删除会删除指定版本的密钥。

访问带版本的密钥

Key/Value 版本 2 的 secrets 引擎维护了可以通过在 Vault 仓库接口声明中实现 RevisionRepository 来访问的 secrets 版本。 修订版本仓库定义了用于获取特定标识符的修订版本的查找方法。 标识符必须是 Stringspring-doc.cadn.net.cn

Example 10. 实现 RevisionRepository
interface RevisionCredentialsRepository extends CrudRepository<Credentials, String>,
                                        RevisionRepository<Credentials, String, Integer> (1)
{

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

用法

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

Example 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));