1. 概述

HashiCorp Vault 是一个用于安全存储和管理敏感信息(如密码、API 密钥等)的工具。它主要解决的是在软件开发过程中如何妥善处理敏感数据的问题。如果你对 Vault 还不太了解,可以参考我们之前的文章:Vault 基础入门

Spring Vault 则是 Spring 生态中针对 HashiCorp Vault 提供的一套抽象封装,简化了在 Spring 应用中与 Vault 的交互方式。

本文将通过一个实际例子,带你快速掌握如何使用 Spring Vault 来存储和读取敏感信息。

2. Maven 依赖配置

首先,我们需要引入 Spring Vault 的核心依赖:

<dependencies>
    <dependency>
        <groupId>org.springframework.vault</groupId>
        <artifactId>spring-vault-core</artifactId>
        <version>3.1.1</version>
    </dependency>
</dependencies>

最新的 spring-vault-core 版本可以在 Maven Central 上找到。

3. 配置 Vault 客户端

要操作 Vault,我们需要先配置好客户端。

3.1. 创建 VaultTemplate

我们可以通过 VaultTemplate 来与 Vault 进行交互。创建它需要两个核心组件:VaultEndpointTokenAuthentication

VaultTemplate vaultTemplate = new VaultTemplate(
    new VaultEndpoint(), 
    new TokenAuthentication("00000000-0000-0000-0000-000000000000")
);

3.2. 配置 VaultEndpoint

有几种方式可以创建 VaultEndpoint 实例:

✅ 默认方式(适用于本地开发):

VaultEndpoint endpoint = new VaultEndpoint();

这会默认连接到 http://localhost:8200

✅ 指定主机和端口:

VaultEndpoint endpoint = VaultEndpoint.create("host", port);

✅ 使用完整 URI:

VaultEndpoint endpoint = VaultEndpoint.from(new URI("vault uri"));

⚠️ 注意:这里我们使用的是一个默认的 root token 00000000-0000-0000-0000-000000000000,仅用于演示,生产环境请勿使用。

除了 TokenAuthentication,Spring Vault 还支持其他多种认证方式,详见官方文档:认证方式

4. 使用 Spring 配置 Vault Bean

在 Spring 中,有两种主要方式来配置 Vault Bean:

  • 继承 AbstractVaultConfiguration
  • 使用 EnvironmentVaultConfiguration 并配合属性文件

我们分别来看一下。

4.1. 使用 AbstractVaultConfiguration

创建一个配置类继承 AbstractVaultConfiguration

@Configuration
public class VaultConfig extends AbstractVaultConfiguration {

    @Override
    public ClientAuthentication clientAuthentication() {
        return new TokenAuthentication("00000000-0000-0000-0000-000000000000");
    }

    @Override
    public VaultEndpoint vaultEndpoint() {
        return VaultEndpoint.create("host", 8020);
    }
}

这种方式和前面手动创建的方式类似,只是封装成了 Spring 配置类,便于管理。

4.2. 使用 EnvironmentVaultConfiguration

另一种方式是使用 EnvironmentVaultConfiguration,通过属性文件来配置:

@Configuration
@PropertySource(value = { "vault-config.properties" })
@Import(value = EnvironmentVaultConfiguration.class)
public class VaultEnvironmentConfig {
}

然后在 vault-config.properties 文件中配置如下内容:

vault.uri=https://localhost:8200
vault.token=00000000-0000-0000-0000-000000000000

更多信息可参考官方文档:EnvironmentVaultConfiguration 配置

5. 存储敏感信息

我们先定义一个简单的实体类 Credentials

public class Credentials {

    private String username;
    private String password;

    // 标准构造函数、getter、setter 省略
}

接着使用 VaultTemplate 来保存它:

Credentials credentials = new Credentials("username", "password");
VaultKeyValueOperations vaultKeyValueOperations = vaultTemplate.opsForKeyValue(
    "credentials/myapp", 
    VaultKeyValueOperationsSupport.KeyValueBackend.KV_2
);
vaultKeyValueOperations.put(credentials.getUsername(), credentials);

这里我们使用了 VaultKeyValueOperations,它支持 KV v1 和 v2 两种格式。

6. 读取敏感信息

通过 get() 方法可以读取之前保存的信息:

VaultResponseSupport<Credentials> response = vaultKeyValueOperations.get(username, Credentials.class);
String username = response.getData().getUsername();
String password = response.getData().getPassword();

这样我们就成功取回了敏感信息 ✅

7. Vault Repository

Spring Vault 2.0 引入了一个非常实用的功能:Vault Repository,它借鉴了 Spring Data Repository 的设计思想,使得操作 Vault 更加面向对象。

我们来看看如何使用这个功能(仅支持 KV v1)。

7.1. @Secret@Id 注解

使用这两个注解可以标记我们要持久化的对象:

@Secret(backend = "credentials", value = "myapp")
public class Credentials {

    @Id
    private String username;
    // 其他字段和方法省略
}
  • @Secret:标识存储路径,backend 是挂载点,value 是路径后缀。
  • @Id:标识主键字段。

7.2. 定义 Repository 接口

定义一个继承 CrudRepository 的接口:

public interface CredentialsRepository extends CrudRepository<Credentials, String> {
}

然后在 Service 中注入并使用:

public class CredentialsService {

    private final VaultTemplate vaultTemplate;
    private CredentialsRepository credentialsRepository;
    private final VaultKeyValueOperations vaultKeyValueOperations;

    @Autowired
    public CredentialsService(VaultTemplate vaultTemplate, CredentialsRepository credentialsRepository) {
        this.vaultTemplate = vaultTemplate;
        this.credentialsRepository = credentialsRepository;
        this.vaultKeyValueOperations = vaultTemplate.opsForKeyValue(
            "credentials/myapp", 
            VaultKeyValueOperationsSupport.KeyValueBackend.KV_2
        );
    }

    public Credentials saveCredentials(Credentials credentials) {
        return credentialsRepository.save(credentials);
    }

    public Optional<Credentials> findById(String username) {
        return credentialsRepository.findById(username);
    }
}

7.3. 测试代码

✅ 保存测试:

@Test
public void givenCredentials_whenSave_thenReturnCredentials() throws InterruptedException {
    Assume.assumeTrue("v1".equals(API_VERSION));

    credentialsService = new CredentialsService(vaultTemplate, credentialsRepository);
    Credentials credentials = new Credentials("login", "password");

    Credentials savedCredentials = credentialsService.saveCredentials(credentials);

    assertNotNull(savedCredentials);
    assertEquals(credentials.getUsername(), savedCredentials.getUsername());
    assertEquals(credentials.getPassword(), savedCredentials.getPassword());
}

✅ 查询测试:

@Test
public void givenId_whenFindById_thenReturnCredentials() {
    Assume.assumeTrue("v1".equals(API_VERSION));
    Credentials expectedCredentials = new Credentials("login", "p@ssw@rd");
    credentialsService.saveCredentials(expectedCredentials);

    Optional<Credentials> retrievedCredentials = credentialsService.findById(expectedCredentials.getUsername());

    assertNotNull(retrievedCredentials);
    assertNotNull(retrievedCredentials.get());
    assertEquals(expectedCredentials.getUsername(), retrievedCredentials.get().getUsername());
    assertEquals(expectedCredentials.getPassword(), retrievedCredentials.get().getPassword());
}

⚠️ 再次提醒:Vault Repository 仅支持未版本化的 KV v1 后端。

8. 总结

在这篇文章中,我们介绍了 Spring Vault 的基本使用方式,包括配置客户端、保存和读取敏感信息,以及使用 Vault Repository 来简化操作。

源码示例已上传到 GitHub:Spring Vault 示例代码


📌 踩坑提醒

  • 使用 KV v2 时,某些 API 行为与 v1 不一致,注意区分。
  • Token 权限不足时会报错,请确保配置正确的策略和权限。
  • 生产环境务必避免使用默认 root token,建议使用 AppRole 或 Kubernetes 认证方式。

原始标题:Spring Vault