Vault Identity for Java Applications

Introduction

HashiCorp Vault Identity provides a powerful identity and access management system that enables secure service-to-service communication and user management. For Java applications, Vault Identity offers robust authentication, authorization, and secrets management capabilities.

Architecture Overview

1. Vault Identity Integration Architecture

Java Application → Vault Client → Vault Server → Identity System
↓                ↓              ↓             ↓
Authentication    Token          Policies      Entities
↓                ↓              ↓             ↓
Authorization     Secrets        Groups        Aliases

Setup and Dependencies

1. Maven Dependencies

<properties>
<vault.version>1.15.0</vault.version>
<spring-vault.version>3.1.0</spring-vault.version>
</properties>
<dependencies>
<!-- Vault Java Driver -->
<dependency>
<groupId>com.bettercloud</groupId>
<artifactId>vault-java-driver</artifactId>
<version>${vault.version}</version>
</dependency>
<!-- Spring Vault -->
<dependency>
<groupId>org.springframework.vault</groupId>
<artifactId>spring-vault-core</artifactId>
<version>${spring-vault.version}</version>
</dependency>
<!-- Spring Cloud Vault -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-vault-config</artifactId>
<version>4.1.0</version>
</dependency>
<!-- JWT for OIDC -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
</dependencies>

Core Vault Identity Configuration

1. Vault Configuration Properties

@Configuration
@ConfigurationProperties(prefix = "vault")
@Data
public class VaultConfigProperties {
private String address = "http://localhost:8200";
private String token;
private String namespace;
private SslConfig ssl = new SslConfig();
private AuthConfig auth = new AuthConfig();
private IdentityConfig identity = new IdentityConfig();
@Data
public static class SslConfig {
private boolean enabled = false;
private String keyStorePath;
private String keyStorePassword;
private String trustStorePath;
private String trustStorePassword;
}
@Data
public static class AuthConfig {
private AuthMethod method = AuthMethod.TOKEN;
private String roleId;
private String secretId;
private String jwtPath;
private String kubernetesRole;
}
@Data
public static class IdentityConfig {
private String entityName;
private String groupName;
private List<String> policies = new ArrayList<>();
private Map<String, String> metadata = new HashMap<>();
}
public enum AuthMethod {
TOKEN, APP_ROLE, KUBERNETES, JWT, USERPASS
}
}

2. Vault Client Configuration

@Configuration
@Slf4j
public class VaultClientConfig {
private final VaultConfigProperties vaultProperties;
public VaultClientConfig(VaultConfigProperties vaultProperties) {
this.vaultProperties = vaultProperties;
}
@Bean
public VaultConfig vaultConfig() {
VaultConfig config = new VaultConfig()
.address(vaultProperties.getAddress())
.engineVersion(2);
if (vaultProperties.getNamespace() != null) {
config.nameSpace(vaultProperties.getNamespace());
}
// Configure SSL if enabled
if (vaultProperties.getSsl().isEnabled()) {
config.sslConfig(createSslConfig());
}
return config.build();
}
@Bean
public Vault vaultClient(VaultConfig vaultConfig) {
return new Vault(vaultConfig);
}
@Bean
@ConditionalOnProperty(name = "vault.auth.method", havingValue = "TOKEN")
public Vault authWithToken(VaultConfig vaultConfig) {
if (vaultProperties.getToken() == null) {
throw new IllegalStateException("Vault token is required for token authentication");
}
VaultConfig config = new VaultConfig(vaultConfig)
.token(vaultProperties.getToken())
.build();
return new Vault(config);
}
@Bean
@ConditionalOnProperty(name = "vault.auth.method", havingValue = "APP_ROLE")
public Vault authWithAppRole(VaultConfig vaultConfig) {
Vault vault = new Vault(vaultConfig);
try {
final AppRoleAuth auth = vault.auth()
.loginByAppRole(
"approle",
vaultProperties.getAuth().getRoleId(),
vaultProperties.getAuth().getSecretId()
);
VaultConfig authedConfig = new VaultConfig(vaultConfig)
.token(auth.getAuthClientToken())
.build();
return new Vault(authedConfig);
} catch (VaultException e) {
throw new RuntimeException("Failed to authenticate with AppRole", e);
}
}
@Bean
@ConditionalOnProperty(name = "vault.auth.method", havingValue = "KUBERNETES")
public Vault authWithKubernetes(VaultConfig vaultConfig) {
Vault vault = new Vault(vaultConfig);
try {
String jwt = readKubernetesToken();
final AuthResponse response = vault.auth()
.loginByKubernetes(
vaultProperties.getAuth().getKubernetesRole(),
jwt
);
VaultConfig authedConfig = new VaultConfig(vaultConfig)
.token(response.getAuthClientToken())
.build();
return new Vault(authedConfig);
} catch (Exception e) {
throw new RuntimeException("Failed to authenticate with Kubernetes", e);
}
}
private SslConfig createSslConfig() {
VaultConfigProperties.SslConfig sslProps = vaultProperties.getSsl();
return new SslConfig()
.keyStoreFile(new File(sslProps.getKeyStorePath()))
.keyStorePassword(sslProps.getKeyStorePassword())
.trustStoreFile(new File(sslProps.getTrustStorePath()))
.trustStorePassword(sslProps.getTrustStorePassword())
.build();
}
private String readKubernetesToken() throws IOException {
String path = vaultProperties.getAuth().getJwtPath();
if (path == null) {
path = "/var/run/secrets/kubernetes.io/serviceaccount/token";
}
return Files.readString(Paths.get(path));
}
}

Vault Identity Service Implementation

1. Core Identity Service

@Service
@Slf4j
public class VaultIdentityService {
private final Vault vault;
private final VaultConfigProperties vaultProperties;
private final ObjectMapper objectMapper;
public VaultIdentityService(Vault vault, 
VaultConfigProperties vaultProperties,
ObjectMapper objectMapper) {
this.vault = vault;
this.vaultProperties = vaultProperties;
this.objectMapper = objectMapper;
}
// Entity Management
public Entity createEntity(String entityName, Map<String, String> metadata, List<String> policies) {
try {
Map<String, Object> data = new HashMap<>();
data.put("name", entityName);
data.put("metadata", metadata != null ? metadata : new HashMap<>());
data.put("policies", policies != null ? policies : new ArrayList<>());
LogicalResponse response = vault.logical()
.write("identity/entity", data);
return parseEntityResponse(response);
} catch (VaultException e) {
log.error("Failed to create entity: {}", entityName, e);
throw new VaultIdentityException("Failed to create entity: " + entityName, e);
}
}
public Entity getEntity(String entityId) {
try {
LogicalResponse response = vault.logical()
.read("identity/entity/id/" + entityId);
return parseEntityResponse(response);
} catch (VaultException e) {
log.error("Failed to get entity: {}", entityId, e);
throw new VaultIdentityException("Failed to get entity: " + entityId, e);
}
}
public List<Entity> listEntities() {
try {
LogicalResponse response = vault.logical()
.read("identity/entity/name");
List<Entity> entities = new ArrayList<>();
Map<String, Object> data = response.getData();
if (data != null && data.containsKey("keys")) {
@SuppressWarnings("unchecked")
List<String> entityNames = (List<String>) data.get("keys");
for (String entityName : entityNames) {
try {
Entity entity = getEntityByName(entityName);
entities.add(entity);
} catch (Exception e) {
log.warn("Failed to fetch entity: {}", entityName, e);
}
}
}
return entities;
} catch (VaultException e) {
log.error("Failed to list entities", e);
throw new VaultIdentityException("Failed to list entities", e);
}
}
public Entity updateEntity(String entityId, Map<String, String> metadata, List<String> policies) {
try {
Entity existingEntity = getEntity(entityId);
Map<String, Object> data = new HashMap<>();
data.put("name", existingEntity.getName());
data.put("metadata", metadata != null ? metadata : existingEntity.getMetadata());
data.put("policies", policies != null ? policies : existingEntity.getPolicies());
LogicalResponse response = vault.logical()
.write("identity/entity/id/" + entityId, data);
return parseEntityResponse(response);
} catch (VaultException e) {
log.error("Failed to update entity: {}", entityId, e);
throw new VaultIdentityException("Failed to update entity: " + entityId, e);
}
}
public void deleteEntity(String entityId) {
try {
vault.logical()
.delete("identity/entity/id/" + entityId);
log.info("Deleted entity: {}", entityId);
} catch (VaultException e) {
log.error("Failed to delete entity: {}", entityId, e);
throw new VaultIdentityException("Failed to delete entity: " + entityId, e);
}
}
// Group Management
public Group createGroup(String groupName, String groupType, List<String> memberEntityIds, 
List<String> policies) {
try {
Map<String, Object> data = new HashMap<>();
data.put("name", groupName);
data.put("type", groupType);
data.put("member_entity_ids", memberEntityIds != null ? memberEntityIds : new ArrayList<>());
data.put("policies", policies != null ? policies : new ArrayList<>());
LogicalResponse response = vault.logical()
.write("identity/group", data);
return parseGroupResponse(response);
} catch (VaultException e) {
log.error("Failed to create group: {}", groupName, e);
throw new VaultIdentityException("Failed to create group: " + groupName, e);
}
}
public Group getGroup(String groupId) {
try {
LogicalResponse response = vault.logical()
.read("identity/group/id/" + groupId);
return parseGroupResponse(response);
} catch (VaultException e) {
log.error("Failed to get group: {}", groupId, e);
throw new VaultIdentityException("Failed to get group: " + groupId, e);
}
}
public void addEntityToGroup(String groupId, String entityId) {
try {
Group group = getGroup(groupId);
List<String> memberEntityIds = new ArrayList<>(group.getMemberEntityIds());
if (!memberEntityIds.contains(entityId)) {
memberEntityIds.add(entityId);
Map<String, Object> data = new HashMap<>();
data.put("name", group.getName());
data.put("type", group.getType());
data.put("member_entity_ids", memberEntityIds);
data.put("policies", group.getPolicies());
vault.logical()
.write("identity/group/id/" + groupId, data);
log.info("Added entity {} to group {}", entityId, groupId);
}
} catch (VaultException e) {
log.error("Failed to add entity to group: {} -> {}", entityId, groupId, e);
throw new VaultIdentityException("Failed to add entity to group", e);
}
}
// Alias Management (for different auth methods)
public EntityAlias createEntityAlias(String entityId, String authBackend, String aliasName) {
try {
Map<String, Object> data = new HashMap<>();
data.put("name", aliasName);
data.put("canonical_id", entityId);
data.put("mount_accessor", getAuthMountAccessor(authBackend));
LogicalResponse response = vault.logical()
.write("identity/entity-alias", data);
return parseAliasResponse(response);
} catch (VaultException e) {
log.error("Failed to create entity alias: {}", aliasName, e);
throw new VaultIdentityException("Failed to create entity alias: " + aliasName, e);
}
}
// Helper methods
private Entity parseEntityResponse(LogicalResponse response) {
try {
Map<String, Object> data = response.getData();
return objectMapper.convertValue(data, Entity.class);
} catch (Exception e) {
throw new VaultIdentityException("Failed to parse entity response", e);
}
}
private Group parseGroupResponse(LogicalResponse response) {
try {
Map<String, Object> data = response.getData();
return objectMapper.convertValue(data, Group.class);
} catch (Exception e) {
throw new VaultIdentityException("Failed to parse group response", e);
}
}
private EntityAlias parseAliasResponse(LogicalResponse response) {
try {
Map<String, Object> data = response.getData();
return objectMapper.convertValue(data, EntityAlias.class);
} catch (Exception e) {
throw new VaultIdentityException("Failed to parse alias response", e);
}
}
private Entity getEntityByName(String entityName) {
try {
LogicalResponse response = vault.logical()
.read("identity/entity/name/" + entityName);
return parseEntityResponse(response);
} catch (VaultException e) {
throw new VaultIdentityException("Failed to get entity by name: " + entityName, e);
}
}
private String getAuthMountAccessor(String authBackend) {
try {
LogicalResponse response = vault.logical()
.read("sys/auth");
Map<String, Object> data = response.getData();
@SuppressWarnings("unchecked")
Map<String, Object> authMounts = (Map<String, Object>) data.get(authBackend + "/");
return (String) authMounts.get("accessor");
} catch (Exception e) {
throw new VaultIdentityException("Failed to get auth mount accessor for: " + authBackend, e);
}
}
}

2. Identity Models

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Entity {
private String id;
private String name;
private Map<String, String> metadata;
private List<String> policies;
private Boolean disabled;
private String creationTime;
private String lastUpdateTime;
private String mergedEntityIds;
private String namespaceId;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class Alias {
private String id;
private String canonicalId;
private String mountAccessor;
private String mountPath;
private String mountType;
private String name;
private Map<String, String> metadata;
private String creationTime;
private String lastUpdateTime;
private Boolean mergedFromCanonicalIds;
}
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Group {
private String id;
private String name;
private String type;
private List<String> policies;
private List<String> memberEntityIds;
private Map<String, String> metadata;
private String creationTime;
private String lastUpdateTime;
private String namespaceId;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class Alias {
private String id;
private String canonicalId;
private String mountAccessor;
private String name;
private String creationTime;
private String lastUpdateTime;
private String lastUpdateTime;
}
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class EntityAlias {
private String id;
private String canonicalId;
private String mountAccessor;
private String name;
private Map<String, String> metadata;
private String creationTime;
private String lastUpdateTime;
}

Authentication and Authorization

1. JWT/OIDC Authentication Service

@Service
@Slf4j
public class VaultAuthService {
private final Vault vault;
private final VaultIdentityService identityService;
private final JwtService jwtService;
public VaultAuthService(Vault vault, 
VaultIdentityService identityService,
JwtService jwtService) {
this.vault = vault;
this.identityService = identityService;
this.jwtService = jwtService;
}
public AuthResponse authenticateWithJwt(String jwtToken, String role) {
try {
Map<String, Object> authData = new HashMap<>();
authData.put("role", role);
authData.put("jwt", jwtToken);
LogicalResponse response = vault.logical()
.write("auth/jwt/login", authData);
return parseAuthResponse(response);
} catch (VaultException e) {
log.error("JWT authentication failed for role: {}", role, e);
throw new VaultAuthException("JWT authentication failed", e);
}
}
public AuthResponse authenticateWithUserPass(String username, String password) {
try {
Map<String, Object> authData = new HashMap<>();
authData.put("password", password);
LogicalResponse response = vault.logical()
.write("auth/userpass/login/" + username, authData);
return parseAuthResponse(response);
} catch (VaultException e) {
log.error("Userpass authentication failed for user: {}", username, e);
throw new VaultAuthException("Userpass authentication failed", e);
}
}
public TokenInfo createToken(String role, List<String> policies, Map<String, String> metadata) {
try {
Map<String, Object> tokenData = new HashMap<>();
tokenData.put("role", role);
tokenData.put("policies", policies != null ? policies : new ArrayList<>());
tokenData.put("meta", metadata != null ? metadata : new HashMap<>());
tokenData.put("ttl", "24h");
tokenData.put("renewable", true);
LogicalResponse response = vault.logical()
.write("auth/token/create", tokenData);
return parseTokenResponse(response);
} catch (VaultException e) {
log.error("Failed to create token for role: {}", role, e);
throw new VaultAuthException("Failed to create token", e);
}
}
public TokenInfo lookupToken(String token) {
try {
Map<String, Object> lookupData = new HashMap<>();
lookupData.put("token", token);
LogicalResponse response = vault.logical()
.write("auth/token/lookup", lookupData);
return parseTokenLookupResponse(response);
} catch (VaultException e) {
log.error("Failed to lookup token", e);
throw new VaultAuthException("Failed to lookup token", e);
}
}
public void revokeToken(String token) {
try {
Map<String, Object> revokeData = new HashMap<>();
revokeData.put("token", token);
vault.logical()
.write("auth/token/revoke", revokeData);
log.info("Revoked token: {}", token);
} catch (VaultException e) {
log.error("Failed to revoke token", e);
throw new VaultAuthException("Failed to revoke token", e);
}
}
public AuthResponse renewToken(String token) {
try {
Map<String, Object> renewData = new HashMap<>();
renewData.put("token", token);
LogicalResponse response = vault.logical()
.write("auth/token/renew", renewData);
return parseAuthResponse(response);
} catch (VaultException e) {
log.error("Failed to renew token", e);
throw new VaultAuthException("Failed to renew token", e);
}
}
// Service account authentication
public ServiceAccount authenticateService(String serviceName, String serviceToken) {
try {
// Validate service token and get associated entity
TokenInfo tokenInfo = lookupToken(serviceToken);
String entityId = tokenInfo.getEntityId();
if (entityId == null) {
throw new VaultAuthException("Token is not associated with an entity");
}
Entity entity = identityService.getEntity(entityId);
return ServiceAccount.builder()
.serviceName(serviceName)
.entity(entity)
.tokenInfo(tokenInfo)
.authenticatedAt(Instant.now())
.build();
} catch (Exception e) {
log.error("Service authentication failed: {}", serviceName, e);
throw new VaultAuthException("Service authentication failed", e);
}
}
private AuthResponse parseAuthResponse(LogicalResponse response) {
try {
Map<String, Object> data = response.getData();
return objectMapper.convertValue(data, AuthResponse.class);
} catch (Exception e) {
throw new VaultAuthException("Failed to parse auth response", e);
}
}
private TokenInfo parseTokenResponse(LogicalResponse response) {
try {
Map<String, Object> data = response.getData();
return objectMapper.convertValue(data, TokenInfo.class);
} catch (Exception e) {
throw new VaultAuthException("Failed to parse token response", e);
}
}
private TokenInfo parseTokenLookupResponse(LogicalResponse response) {
try {
Map<String, Object> data = response.getData();
return objectMapper.convertValue(data, TokenInfo.class);
} catch (Exception e) {
throw new VaultAuthException("Failed to parse token lookup response", e);
}
}
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AuthResponse {
private String clientToken;
private List<String> policies;
private Map<String, String> metadata;
private String entityId;
private Boolean renewable;
private Integer leaseDuration;
private String accessor;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class TokenInfo {
private String id;
private String entityId;
private List<String> policies;
private Map<String, String> meta;
private Boolean renewable;
private Integer ttl;
private String creationTime;
private String expirationTime;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ServiceAccount {
private String serviceName;
private Entity entity;
private TokenInfo tokenInfo;
private Instant authenticatedAt;
private List<String> grantedPolicies;
}

2. JWT Service for OIDC

@Service
@Slf4j
public class JwtService {
private final SecretKey secretKey;
private final ObjectMapper objectMapper;
public JwtService(@Value("${vault.jwt.secret:default-secret-key}") String secretKey) {
this.secretKey = Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8));
this.objectMapper = new ObjectMapper();
}
public String generateServiceToken(String serviceName, Map<String, Object> claims, Duration validity) {
try {
Instant now = Instant.now();
Instant expiry = now.plus(validity);
Map<String, Object> enhancedClaims = new HashMap<>(claims);
enhancedClaims.put("service", serviceName);
enhancedClaims.put("iss", "vault-identity-service");
return Jwts.builder()
.setClaims(enhancedClaims)
.setSubject(serviceName)
.setIssuedAt(Date.from(now))
.setExpiration(Date.from(expiry))
.signWith(secretKey, SignatureAlgorithm.HS256)
.compact();
} catch (Exception e) {
log.error("Failed to generate JWT token for service: {}", serviceName, e);
throw new JwtException("Failed to generate JWT token", e);
}
}
public JwtClaims parseAndValidateToken(String token) {
try {
Claims claims = Jwts.parserBuilder()
.setSigningKey(secretKey)
.build()
.parseClaimsJws(token)
.getBody();
return JwtClaims.builder()
.subject(claims.getSubject())
.issuer(claims.getIssuer())
.issuedAt(claims.getIssuedAt().toInstant())
.expiration(claims.getExpiration().toInstant())
.claims(new HashMap<>(claims))
.build();
} catch (Exception e) {
log.error("Failed to parse or validate JWT token", e);
throw new JwtException("Invalid JWT token", e);
}
}
public boolean validateToken(String token) {
try {
parseAndValidateToken(token);
return true;
} catch (JwtException e) {
return false;
}
}
public String refreshToken(String token, Duration newValidity) {
try {
JwtClaims existingClaims = parseAndValidateToken(token);
Instant now = Instant.now();
Instant expiry = now.plus(newValidity);
return Jwts.builder()
.setClaims(existingClaims.getClaims())
.setSubject(existingClaims.getSubject())
.setIssuedAt(Date.from(now))
.setExpiration(Date.from(expiry))
.signWith(secretKey, SignatureAlgorithm.HS256)
.compact();
} catch (Exception e) {
log.error("Failed to refresh JWT token", e);
throw new JwtException("Failed to refresh JWT token", e);
}
}
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class JwtClaims {
private String subject;
private String issuer;
private Instant issuedAt;
private Instant expiration;
private Map<String, Object> claims;
}

Secrets Management with Identity

1. Dynamic Secrets Service

@Service
@Slf4j
public class VaultSecretsService {
private final Vault vault;
private final VaultAuthService authService;
public VaultSecretsService(Vault vault, VaultAuthService authService) {
this.vault = vault;
this.authService = authService;
}
public DatabaseCredentials getDatabaseCredentials(String role, String mountPath) {
try {
if (mountPath == null) {
mountPath = "database";
}
LogicalResponse response = vault.logical()
.read(mountPath + "/creds/" + role);
return parseDatabaseCredentials(response);
} catch (VaultException e) {
log.error("Failed to get database credentials for role: {}", role, e);
throw new VaultSecretsException("Failed to get database credentials", e);
}
}
public AwsCredentials getAwsCredentials(String role, String mountPath) {
try {
if (mountPath == null) {
mountPath = "aws";
}
LogicalResponse response = vault.logical()
.read(mountPath + "/creds/" + role);
return parseAwsCredentials(response);
} catch (VaultException e) {
log.error("Failed to get AWS credentials for role: {}", role, e);
throw new VaultSecretsException("Failed to get AWS credentials", e);
}
}
public void storeSecret(String path, Map<String, Object> secretData) {
try {
vault.logical()
.write(path, secretData);
log.info("Stored secret at path: {}", path);
} catch (VaultException e) {
log.error("Failed to store secret at path: {}", path, e);
throw new VaultSecretsException("Failed to store secret", e);
}
}
public Map<String, Object> getSecret(String path) {
try {
LogicalResponse response = vault.logical()
.read(path);
return response.getData();
} catch (VaultException e) {
log.error("Failed to get secret from path: {}", path, e);
throw new VaultSecretsException("Failed to get secret", e);
}
}
public void deleteSecret(String path) {
try {
vault.logical()
.delete(path);
log.info("Deleted secret at path: {}", path);
} catch (VaultException e) {
log.error("Failed to delete secret at path: {}", path, e);
throw new VaultSecretsException("Failed to delete secret", e);
}
}
// Dynamic secret leasing
public SecretLease createSecretLease(String path, Map<String, Object> data, Duration ttl) {
try {
Map<String, Object> leaseData = new HashMap<>(data);
leaseData.put("ttl", ttl.getSeconds() + "s");
LogicalResponse response = vault.logical()
.write(path, leaseData);
return parseSecretLease(response);
} catch (VaultException e) {
log.error("Failed to create secret lease at path: {}", path, e);
throw new VaultSecretsException("Failed to create secret lease", e);
}
}
public void renewSecretLease(String leaseId) {
try {
Map<String, Object> renewData = new HashMap<>();
renewData.put("lease_id", leaseId);
vault.logical()
.write("sys/leases/renew", renewData);
log.info("Renewed secret lease: {}", leaseId);
} catch (VaultException e) {
log.error("Failed to renew secret lease: {}", leaseId, e);
throw new VaultSecretsException("Failed to renew secret lease", e);
}
}
public void revokeSecretLease(String leaseId) {
try {
Map<String, Object> revokeData = new HashMap<>();
revokeData.put("lease_id", leaseId);
vault.logical()
.write("sys/leases/revoke", revokeData);
log.info("Revoked secret lease: {}", leaseId);
} catch (VaultException e) {
log.error("Failed to revoke secret lease: {}", leaseId, e);
throw new VaultSecretsException("Failed to revoke secret lease", e);
}
}
private DatabaseCredentials parseDatabaseCredentials(LogicalResponse response) {
Map<String, Object> data = response.getData();
return DatabaseCredentials.builder()
.username((String) data.get("username"))
.password((String) data.get("password"))
.leaseId((String) data.get("lease_id"))
.leaseDuration((Integer) data.get("lease_duration"))
.renewable((Boolean) data.get("renewable"))
.build();
}
private AwsCredentials parseAwsCredentials(LogicalResponse response) {
Map<String, Object> data = response.getData();
return AwsCredentials.builder()
.accessKey((String) data.get("access_key"))
.secretKey((String) data.get("secret_key"))
.securityToken((String) data.get("security_token"))
.leaseId((String) data.get("lease_id"))
.leaseDuration((Integer) data.get("lease_duration"))
.build();
}
private SecretLease parseSecretLease(LogicalResponse response) {
Map<String, Object> data = response.getData();
return SecretLease.builder()
.leaseId((String) data.get("lease_id"))
.leaseDuration((Integer) data.get("lease_duration"))
.renewable((Boolean) data.get("renewable"))
.data(data)
.build();
}
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DatabaseCredentials {
private String username;
private String password;
private String leaseId;
private Integer leaseDuration;
private Boolean renewable;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AwsCredentials {
private String accessKey;
private String secretKey;
private String securityToken;
private String leaseId;
private Integer leaseDuration;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SecretLease {
private String leaseId;
private Integer leaseDuration;
private Boolean renewable;
private Map<String, Object> data;
}

Spring Boot Integration

1. Spring Vault Configuration

@Configuration
@EnableVaultRepositories
@Slf4j
public class SpringVaultConfig {
@Bean
public VaultTemplate vaultTemplate(VaultEndpoint vaultEndpoint, 
ClientAuthentication clientAuthentication) {
return new VaultTemplate(vaultEndpoint, clientAuthentication);
}
@Bean
public VaultEndpoint vaultEndpoint(VaultConfigProperties vaultProperties) {
VaultEndpoint endpoint = VaultEndpoint.from(URI.create(vaultProperties.getAddress()));
if (vaultProperties.getNamespace() != null) {
endpoint.setNamespace(vaultProperties.getNamespace());
}
return endpoint;
}
@Bean
@ConditionalOnProperty(name = "vault.auth.method", havingValue = "TOKEN")
public ClientAuthentication tokenAuthentication(VaultConfigProperties vaultProperties) {
return new TokenAuthentication(vaultProperties.getToken());
}
@Bean
@ConditionalOnProperty(name = "vault.auth.method", havingValue = "APP_ROLE")
public ClientAuthentication appRoleAuthentication(VaultConfigProperties vaultProperties) {
return new AppRoleAuthentication(
vaultProperties.getAuth().getRoleId(),
vaultProperties.getAuth().getSecretId()
);
}
@Bean
@ConditionalOnProperty(name = "vault.auth.method", havingValue = "KUBERNETES")
public ClientAuthentication kubernetesAuthentication(VaultConfigProperties vaultProperties) {
try {
String jwt = Files.readString(Paths.get(
vaultProperties.getAuth().getJwtPath() != null ? 
vaultProperties.getAuth().getJwtPath() : 
"/var/run/secrets/kubernetes.io/serviceaccount/token"
));
return new KubernetesAuthentication(
vaultProperties.getAuth().getKubernetesRole(),
jwt
);
} catch (IOException e) {
throw new RuntimeException("Failed to read Kubernetes service account token", e);
}
}
}

2. Vault-based Configuration

@Component
@Slf4j
public class VaultConfigurationProvider {
private final VaultSecretsService secretsService;
private final VaultTemplate vaultTemplate;
public VaultConfigurationProvider(VaultSecretsService secretsService,
VaultTemplate vaultTemplate) {
this.secretsService = secretsService;
this.vaultTemplate = vaultTemplate;
}
@PostConstruct
public void loadConfigurationFromVault() {
try {
// Load application configuration from Vault
VaultResponse response = vaultTemplate.read("secret/data/application");
if (response != null && response.getData() != null) {
@SuppressWarnings("unchecked")
Map<String, Object> configData = (Map<String, Object>) response.getData().get("data");
applyConfiguration(configData);
}
// Load database credentials
DatabaseCredentials dbCreds = secretsService.getDatabaseCredentials("app-role", "database");
applyDatabaseCredentials(dbCreds);
} catch (Exception e) {
log.warn("Failed to load configuration from Vault, using defaults", e);
}
}
@Scheduled(fixedRate = 300000) // Renew every 5 minutes
public void renewDynamicSecrets() {
try {
// Implement secret renewal logic
log.debug("Checking for secret renewals...");
} catch (Exception e) {
log.error("Failed to renew secrets", e);
}
}
private void applyConfiguration(Map<String, Object> configData) {
// Apply configuration to Spring Environment
log.info("Applied configuration from Vault: {} keys", configData.size());
}
private void applyDatabaseCredentials(DatabaseCredentials credentials) {
// Update datasource configuration with dynamic credentials
log.info("Applied database credentials from Vault");
}
}

REST API Controllers

1. Identity Management API

@RestController
@RequestMapping("/api/v1/identity")
@Slf4j
public class IdentityController {
private final VaultIdentityService identityService;
private final VaultAuthService authService;
public IdentityController(VaultIdentityService identityService,
VaultAuthService authService) {
this.identityService = identityService;
this.authService = authService;
}
@PostMapping("/entities")
public ResponseEntity<Entity> createEntity(@RequestBody CreateEntityRequest request) {
try {
Entity entity = identityService.createEntity(
request.getName(), 
request.getMetadata(), 
request.getPolicies()
);
return ResponseEntity.status(HttpStatus.CREATED).body(entity);
} catch (Exception e) {
log.error("Failed to create entity: {}", request.getName(), e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
@GetMapping("/entities")
public ResponseEntity<List<Entity>> listEntities() {
try {
List<Entity> entities = identityService.listEntities();
return ResponseEntity.ok(entities);
} catch (Exception e) {
log.error("Failed to list entities", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
@GetMapping("/entities/{entityId}")
public ResponseEntity<Entity> getEntity(@PathVariable String entityId) {
try {
Entity entity = identityService.getEntity(entityId);
return ResponseEntity.ok(entity);
} catch (VaultIdentityException e) {
return ResponseEntity.notFound().build();
} catch (Exception e) {
log.error("Failed to get entity: {}", entityId, e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
@PostMapping("/tokens")
public ResponseEntity<TokenInfo> createToken(@RequestBody CreateTokenRequest request) {
try {
TokenInfo tokenInfo = authService.createToken(
request.getRole(),
request.getPolicies(),
request.getMetadata()
);
return ResponseEntity.status(HttpStatus.CREATED).body(tokenInfo);
} catch (Exception e) {
log.error("Failed to create token", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
@PostMapping("/auth/jwt")
public ResponseEntity<AuthResponse> authenticateWithJwt(@RequestBody JwtAuthRequest request) {
try {
AuthResponse response = authService.authenticateWithJwt(
request.getJwt(), 
request.getRole()
);
return ResponseEntity.ok(response);
} catch (VaultAuthException e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
} catch (Exception e) {
log.error("JWT authentication failed", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
class CreateEntityRequest {
private String name;
private Map<String, String> metadata;
private List<String> policies;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
class CreateTokenRequest {
private String role;
private List<String> policies;
private Map<String, String> metadata;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
class JwtAuthRequest {
private String jwt;
private String role;
}

Security and Error Handling

1. Custom Exceptions

public class VaultIdentityException extends RuntimeException {
public VaultIdentityException(String message) {
super(message);
}
public VaultIdentityException(String message, Throwable cause) {
super(message, cause);
}
}
public class VaultAuthException extends RuntimeException {
public VaultAuthException(String message) {
super(message);
}
public VaultAuthException(String message, Throwable cause) {
super(message, cause);
}
}
public class VaultSecretsException extends RuntimeException {
public VaultSecretsException(String message) {
super(message);
}
public VaultSecretsException(String message, Throwable cause) {
super(message, cause);
}
}
public class JwtException extends RuntimeException {
public JwtException(String message) {
super(message);
}
public JwtException(String message, Throwable cause) {
super(message, cause);
}
}

2. Global Exception Handler

@ControllerAdvice
@Slf4j
public class VaultExceptionHandler {
@ExceptionHandler(VaultAuthException.class)
public ResponseEntity<ErrorResponse> handleVaultAuthException(VaultAuthException e) {
log.warn("Vault authentication error", e);
ErrorResponse error = ErrorResponse.builder()
.code("VAULT_AUTH_ERROR")
.message("Authentication failed")
.details(e.getMessage())
.timestamp(Instant.now())
.build();
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(error);
}
@ExceptionHandler(VaultIdentityException.class)
public ResponseEntity<ErrorResponse> handleVaultIdentityException(VaultIdentityException e) {
log.error("Vault identity error", e);
ErrorResponse error = ErrorResponse.builder()
.code("VAULT_IDENTITY_ERROR")
.message("Identity operation failed")
.details(e.getMessage())
.timestamp(Instant.now())
.build();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
}
@ExceptionHandler(VaultSecretsException.class)
public ResponseEntity<ErrorResponse> handleVaultSecretsException(VaultSecretsException e) {
log.error("Vault secrets error", e);
ErrorResponse error = ErrorResponse.builder()
.code("VAULT_SECRETS_ERROR")
.message("Secrets operation failed")
.details(e.getMessage())
.timestamp(Instant.now())
.build();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
}
@Data
@Builder
public static class ErrorResponse {
private String code;
private String message;
private String details;
private Instant timestamp;
}
}

Conclusion

Vault Identity provides Java applications with a comprehensive identity and access management solution that enables:

  1. Centralized Identity Management - Unified entity and group management
  2. Flexible Authentication - Multiple auth methods (JWT, AppRole, Kubernetes, etc.)
  3. Dynamic Secrets - On-demand credentials with automatic rotation
  4. Fine-grained Authorization - Policy-based access control
  5. Service-to-Service Security - Secure communication between microservices

Key benefits for Java applications:

  • Enhanced Security - Centralized secrets management and identity verification
  • Operational Efficiency - Automated credential rotation and lifecycle management
  • Compliance - Audit trails and access logging
  • Scalability - Designed for microservices and cloud-native architectures
  • Developer Experience - Clean APIs and Spring Boot integration

By implementing Vault Identity with the patterns shown above, Java applications can achieve enterprise-grade security while maintaining developer productivity and operational efficiency.

Java Logistics, Shipping Integration & Enterprise Inventory Automation (Tracking, ERP, RFID & Billing Systems)

https://macronepal.com/blog/aftership-tracking-in-java-enterprise-package-visibility/
Explains how to integrate AfterShip tracking services into Java applications to provide real-time shipment visibility, delivery status updates, and centralized tracking across multiple courier services.

https://macronepal.com/blog/shipping-integration-using-fedex-api-with-java-for-logistics-automation/
Explains how to integrate the FedEx API into Java systems to automate shipping tasks such as creating shipments, calculating delivery costs, generating shipping labels, and tracking packages.

https://macronepal.com/blog/shipping-and-logistics-integrating-ups-apis-with-java-applications/
Explains UPS API integration in Java to enable automated shipping operations including rate calculation, shipment scheduling, tracking, and delivery confirmation management.

https://macronepal.com/blog/generating-and-reading-qr-codes-for-products-in-java/
Explains how Java applications generate and read QR codes for product identification, tracking, and authentication, supporting faster inventory handling and product verification processes.

https://macronepal.com/blog/designing-a-robust-pick-and-pack-workflow-in-java/
Explains how to design an efficient pick-and-pack workflow in Java warehouse systems, covering order processing, item selection, packaging steps, and logistics preparation to improve fulfillment efficiency.

https://macronepal.com/blog/rfid-inventory-management-system-in-java-a-complete-guide/
Explains how RFID technology integrates with Java applications to automate inventory tracking, reduce manual errors, and enable real-time stock monitoring in warehouses and retail environments.

https://macronepal.com/blog/erp-integration-with-odoo-in-java/
Explains how Java applications connect with Odoo ERP systems to synchronize inventory, orders, customer records, and financial data across enterprise systems.

https://macronepal.com/blog/automated-invoice-generation-creating-professional-excel-invoices-with-apache-poi-in-java/
Explains how to automatically generate professional Excel invoices in Java using Apache POI, enabling structured billing documents and automated financial record creation.

https://macronepal.com/blog/enterprise-financial-integration-using-quickbooks-api-in-java-applications/
Explains QuickBooks API integration in Java to automate financial workflows such as invoice management, payment tracking, accounting synchronization, and financial reporting.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper