VaultReader Class
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.http.*;
import org.springframework.web.client.HttpServerErrorException;
import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
import java.time.Duration;
import java.util.Base64;
import java.util.UUID;
import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;
import com.jayway.jsonpath.PathNotFoundException;
public class VaultReader {
private final Logger logger = LoggerFactory.getLogger(VaultReader.class);
private final RestTemplate restTemplate;
private final String vaultUrl;
private final String username;
private final String password;
public VaultReader(String vaultUrl, String username, String password) {
this.restTemplate = new RestTemplate();
this.vaultUrl = vaultUrl;
this.username = username;
this.password = password;
}
@Cacheable(value = "vaultToken", key = "#username")
public TokenWrapper getTokenWrapper(String username){
return null;
}
// Remove the token from the cache when it's expired or revoked.
@Caching(evict = {@CacheEvict(value="vaultToken", key="#username")})
public void evictToken(String username){
}
public String readFromVault(String path, String jsonPath) throws HttpServerErrorException, PathNotFoundException, RestClientException {
try {
// Get an available token from cache or request a new one.
TokenWrapper tokenWrapper= getTokenWrapper(username);
if (isTokenExpired(tokenWrapper.expireTime)) {
evictToken(username);
tokenWrapper=getNewTokenWrapper(username,password);
}
// Set HTTP Authorization Header with the available token and execute requests.
HttpHeaders headers = new HttpHeaders();
headers.set("X-Vault-Token", tokenWrapper.token);
HttpEntity<Object> entity = new HttpEntity<>(headers);
String url = String.format("%s/v1/%s", vaultUrl, path);
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class);
if(response.getStatusCode() != HttpStatus.OK) {
// Remove the token from cache when HTTP status code indicates an invalid or unauthorized request.
evictToken(username);
throw new HttpServerErrorException(response.getStatusCode(), "Vault request failed with status code " + response.getStatusCodeValue());
}
String responseBody = response.getBody();
if (responseBody == null) {
return null;
}
DocumentContext documentContext = JsonPath.parse(responseBody);
Object result = documentContext.read(jsonPath);
if(result == null){
throw new PathNotFoundException(String.format("No result found for JSONPath %s", jsonPath));
}
return result.toString();
} catch (HttpStatusCodeException e) {
logger.error("HTTP request failed with status code {}: {}", e.getStatusCode(), e.getResponseBodyAsString());
if (e.getStatusCode().is4xxClientError()) {
evictToken(username);
}
throw e;
} catch (RestClientException | IllegalStateException e) {
logger.error("HTTP client exception: ", e);
evictToken(username);
throw e;
}
}
private void storeTokenWrapper(String username, TokenWrapper tokenWrapper){
// This method is intentionally left blank.
// The Spring Cache will take care of storing the tokenWrapper object
// into the cache with expiration settings.
}
private TokenWrapper getNewTokenWrapper(String username, String password) {
HttpHeaders headers = new HttpHeaders();
byte[] authBytes = Base64.getEncoder().encode(String.format("%s:%s", username, password).getBytes());
String base64Auth = new String(authBytes);
headers.set("Authorization", "Basic "+base64Auth);
HttpEntity<String> requestEntity = new HttpEntity<>("", headers);
String url = String.format("%s/v1/auth/userpass/login/%s", vaultUrl, username);
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class);
if(response.getStatusCode() != HttpStatus.OK) {
evictToken(username);
throw new HttpServerErrorException(response.getStatusCode(), "Vault login request failed with status code " + response.getStatusCodeValue());
}
String token = response.getHeaders().getFirst("X-Vault-Token");
String expireTimeString=response.getBody();
JsonObject jsonObject = JsonParser.parseString(expireTimeString).getAsJsonObject();
long ttl=jsonObject.get("auth").getAsJsonObject().get("lease_duration").getAsLong();
TokenWrapper tokenWrapper=new TokenWrapper();
tokenWrapper.token=token;
// Calculate the expiration time based on the Time-To-Live (TTL) returned by Vault API.
tokenWrapper.expireTime=System.currentTimeMillis()+ Duration.ofSeconds(ttl).toMillis()/2;
storeTokenWrapper(username,tokenWrapper);
return tokenWrapper;
}
private boolean isTokenExpired(long expireTime) {
long timeDiff = expireTime - System.currentTimeMillis();
return timeDiff <= 0;
}
class TokenWrapper{
String token;
long expireTime;
UUID uuid=UUID.randomUUID();
}
}
以下是在 Spring Boot 中如何使用 VaultReader class 的一个例子:
- 添加Maven依赖
首先,在你的 pom.xml 文件中添加以下 Maven 依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<version>2.6.0</version>
</dependency>
其中 ${spring-boot.version} 应该替换为你所使用的 Spring Boot 版本。
- 声明 VaultReader
在某个需要使用 Vault 的类中,声明一个 VaultReader 对象并初始化它:
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class MyService {
private final VaultReader vaultReader;
public MyService(@Value("${vault.url}") String url,
@Value("${vault.username}") String username,
@Value("${vault.password}") String password) {
this.vaultReader = new VaultReader(url, username, password);
}
// 接下来实现自己的业务逻辑 ...
}
在这里,我们使用构造函数注入了 Vault 的 URL、用户名和密码,并创建了一个 VaultReader 对象。
请注意,为了使用 @Value 注解注入属性值,你需要在你的 Spring Boot 应用程序中定义这些属性。这可以通过 application.properties 或 application.yml 文件完成。例如,以下是一个配置文件示例:
vault:
url: http://localhost:8200
username: myusername
password: mysecretpassword
- 使用 VaultReader 读取数据
现在,我们已经完成了 VaultReader 的初始化。接下来,使用 readFromVault(String path, String jsonPath) 方法从 Vault 中读取数据:
public class MyService {
// ...
public String readMySecret() {
// Read the secret named "myapp/config/mysecret".
// We want to extract the "password" field from this secret.
String value = vaultReader.readFromVault("myapp/config/mysecret", "$.data.password");
return value;
}
}
在上面的例子中,我们通过调用 readFromVault() 方法,从 Vault 中读取名为“myapp/config/mysecret”的密钥,并从其数据中提取密码字段。
注意:在运行代码之前,确保你已经开启了 Vault Server 并创建了适当的密钥以供测试使用