Vault 儲存庫

透過使用 VaultTemplate 以及對應至 Java 類別的回應,可以執行基本的資料操作,例如讀取、寫入和刪除。Vault 儲存庫將 Spring Data 的儲存庫概念應用於 Vault 之上。Vault 儲存庫公開基本的 CRUD 功能,並支援使用限制識別碼屬性的述詞、分頁和排序進行查詢衍生。Vault 儲存庫使用金鑰值密碼引擎功能來持久化和查詢資料。從 2.4 版開始,Spring Vault 可以額外使用金鑰值版本 2 密碼引擎,實際的密碼引擎版本會在執行時探索。

版本化金鑰值密碼引擎中的刪除作業使用 DELETE 操作。密碼不會透過 CrudRepository.delete(…) 銷毀。
請閱讀 Spring Data Commons 參考文件,以深入了解 Spring Data 儲存庫。參考文件將為您介紹 Spring Data 儲存庫。

用法

若要存取儲存在 Vault 中的網域實體,您可以利用儲存庫支援,大幅簡化實作。

範例 1. 範例 Credentials 實體
@Secret
class Credentials {

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

我們有一個非常簡單的網域物件。請注意,它有一個名為 id 的屬性,並使用 org.springframework.data.annotation.Id 進行註解,並且在其類型上具有 @Secret 註解。這兩者負責建立實際的金鑰,用於將物件以 JSON 格式持久化在 Vault 中。

使用 @Id 註解以及名稱為 id 的屬性都視為識別碼屬性。具有註解的屬性優先於其他屬性。

下一步是宣告使用網域物件的儲存庫介面。

範例 2. Credentials 實體的基本儲存庫介面
interface CredentialsRepository extends CrudRepository<Credentials, String> {

}

由於我們的儲存庫擴充了 CrudRepository,因此它提供了基本的 CRUD 和查詢方法。Vault 儲存庫需要 Spring Data 元件。請確保在您的類別路徑中包含 spring-data-commonsspring-data-keyvalue 構件。

最簡單的達成方式是設定相依性管理,並將構件新增至您的 pom.xml

然後將以下內容新增至 pom.xml 相依性區段。

範例 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>

我們需要在中間將它們連結在一起的東西是相應的 Spring 設定。

範例 4. Vault 儲存庫的 JavaConfig
@Configuration
@EnableVaultRepositories
class ApplicationConfig {

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

假設上述設定,我們可以繼續將 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)
}
1 Credentials 的屬性儲存在 Vault Hash 中,金鑰模式為 keyspace/id,在本例中為 credentials/heisenberg,在金鑰值密碼引擎中。
2 使用提供的 id 來檢索儲存在 keyspace/id 的物件。
3 計算由 Credentials@Secret 定義的 keyspace credentials 內可用的實體總數。
4 從 Vault 移除給定物件的金鑰。

物件至 Vault JSON 對應

Vault 儲存庫使用 JSON 作為交換格式,將物件儲存在 Vault 中。JSON 和實體之間的物件對應由 VaultConverter 完成。轉換器讀取和寫入 SecretDocument,其中包含來自 VaultResponse 的本文。VaultResponse 是從 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"
  }
}
1 _class 屬性包含在根層級以及任何巢狀介面或抽象類型上。
2 簡單屬性值會依路徑對應。
3 複雜型別的屬性會對應為巢狀物件。
@Id 屬性必須對應至 String
表 1. 預設對應規則
型別 範例 對應值

簡單型別
(例如:String)

String firstname = "Walter";

"firstname": "Walter"

複雜型別
(例如:Address)

Address adress = 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、選擇性地使用偏移/限制來限制 Id 串流,並在擷取結果後套用排序。

範例 6. 範例儲存庫查詢方法
interface CredentialsRepository extends CrudRepository<Credentials, String> {

  List<Credentials> findByIdStartsWith(String prefix);
}
Vault 儲存庫的查詢方法僅支援在 @Id 屬性上使用述詞的查詢。

以下是 Vault 支援的關鍵字概觀。

表 2. 查詢方法支援的關鍵字
關鍵字 範例

AfterGreaterThan

findByIdGreaterThan(String id)

GreaterThanEqual

findByIdGreaterThanEqual(String id)

BeforeLessThan

findByIdLessThan(String id)

LessThanEqual

findByIdLessThanEqual(String id)

Between

findByIdBetween(String from, String to)

In

findByIdIn(Collection ids)

NotIn

findByIdNotIn(Collection ids)

LikeStartingWithEndingWith

findByIdLike(String id)

NotLikeIsNotLike

findByIdNotLike(String id)

Containing

findByFirstnameContaining(String id)

NotContaining

findByFirstnameNotContaining(String name)

Regex

findByIdRegex(String id)

(無關鍵字)

findById(String name)

Not

findByIdNot(String id)

And

findByLastnameAndFirstname

Or

findByLastnameOrFirstname

Is、Equals

findByFirstnamefindByFirstnameIsfindByFirstnameEquals

Top、First

findFirst10ByFirstnamefindTop5ByFirstname

排序與分頁

查詢方法支援排序和分頁,方法是在記憶體中選取從 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);
}

樂觀鎖定

Vault 的金鑰值密碼引擎版本 2 可以維護版本化密碼。Spring Vault 透過網域模型中以 @Version 註解的版本屬性來支援版本控制。使用樂觀鎖定可確保更新僅套用至具有相符版本的密碼。因此,版本屬性的實際值會透過 cas 屬性新增至更新請求。如果另一個操作在此期間變更了密碼,則會擲回 OptimisticLockingFailureException,並且不會更新密碼。

版本屬性必須是數值屬性,例如 intlong,並且在更新密碼時對應至 cas 屬性。

範例 8. 範例版本化實體
@Secret
class VersionedCredentials {

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

以下範例顯示這些功能

範例 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 更新使用先前版本的第二個實例。由於 Vault 中的版本已在此期間遞增,因此操作失敗並出現 OptimisticLockingFailureException
刪除版本化密碼時,依 Id 刪除會刪除最新的密碼。依實體刪除會刪除指定版本的密碼。

存取版本化密碼

金鑰值版本 2 密碼引擎維護可以透過在您的 Vault 儲存庫介面宣告中實作 RevisionRepository 來存取的密碼版本。修訂儲存庫定義了查詢方法,以取得特定識別碼的修訂。識別碼必須是 String

範例 10. 實作 RevisionRepository
interface RevisionCredentialsRepository extends CrudRepository<Credentials, String>,
                                        RevisionRepository<Credentials, String, Integer> (1)
{

}
1 第一個型別參數 (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));