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