Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:28:34 +08:00
commit 390afca02b
220 changed files with 86013 additions and 0 deletions

View File

@@ -0,0 +1,342 @@
---
name: aws-sdk-java-v2-secrets-manager
description: AWS Secrets Manager patterns using AWS SDK for Java 2.x. Use when storing/retrieving secrets (passwords, API keys, tokens), rotating secrets automatically, managing database credentials, or integrating secret management into Spring Boot applications.
category: aws
tags: [aws, secrets-manager, java, sdk, security, credentials, spring-boot]
version: 1.1.0
allowed-tools: Read, Write, Glob, Bash
---
# AWS SDK for Java 2.x - AWS Secrets Manager
## When to Use
Use this skill when:
- Storing and retrieving application secrets programmatically
- Managing database credentials securely without hardcoding
- Implementing automatic secret rotation with Lambda functions
- Integrating AWS Secrets Manager with Spring Boot applications
- Setting up secret caching for improved performance
- Creating secure configuration management systems
- Working with multi-region secret deployments
- Implementing audit logging for secret access
## Dependencies
### Maven
```xml
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>secretsmanager</artifactId>
</dependency>
<!-- For secret caching (recommended for production) -->
<dependency>
<groupId>com.amazonaws.secretsmanager</groupId>
<artifactId>aws-secretsmanager-caching-java</artifactId>
<version>2.0.0</version> // Use the sdk v2 compatible version
</dependency>
```
### Gradle
```gradle
implementation 'software.amazon.awssdk:secretsmanager'
implementation 'com.amazonaws.secretsmanager:aws-secretsmanager-caching-java:2.0.0
```
## Quick Start
### Basic Client Setup
```java
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient;
SecretsManagerClient secretsClient = SecretsManagerClient.builder()
.region(Region.US_EAST_1)
.build();
```
### Store a Secret
```java
import software.amazon.awssdk.services.secretsmanager.model.*;
public String createSecret(String secretName, String secretValue) {
CreateSecretRequest request = CreateSecretRequest.builder()
.name(secretName)
.secretString(secretValue)
.build();
CreateSecretResponse response = secretsClient.createSecret(request);
return response.arn();
}
```
### Retrieve a Secret
```java
public String getSecretValue(String secretName) {
GetSecretValueRequest request = GetSecretValueRequest.builder()
.secretId(secretName)
.build();
GetSecretValueResponse response = secretsClient.getSecretValue(request);
return response.secretString();
}
```
## Core Operations
### Secret Management
- Create secrets with `createSecret()`
- Retrieve secrets with `getSecretValue()`
- Update secrets with `updateSecret()`
- Delete secrets with `deleteSecret()`
- List secrets with `listSecrets()`
- Restore deleted secrets with `restoreSecret()`
### Secret Versioning
- Access specific versions by `versionId`
- Access versions by stage (e.g., "AWSCURRENT", "AWSPENDING")
- Automatically manage version history
### Secret Rotation
- Configure automatic rotation schedules
- Lambda-based rotation functions
- Immediate rotation with `rotateSecret()`
## Caching for Performance
### Setup Cache
```java
import com.amazonaws.secretsmanager.caching.SecretCache;
public class CachedSecrets {
private final SecretCache cache;
public CachedSecrets(SecretsManagerClient secretsClient) {
this.cache = new SecretCache(secretsClient);
}
public String getCachedSecret(String secretName) {
return cache.getSecretString(secretName);
}
}
```
### Cache Configuration
```java
import com.amazonaws.secretsmanager.caching.SecretCacheConfiguration;
SecretCacheConfiguration config = SecretCacheConfiguration.builder()
.maxCacheSize(1000)
.cacheItemTTL(3600000) // 1 hour
.build();
```
## Spring Boot Integration
### Configuration
```java
@Configuration
public class SecretsManagerConfiguration {
@Bean
public SecretsManagerClient secretsManagerClient() {
return SecretsManagerClient.builder()
.region(Region.of(region))
.build();
}
@Bean
public SecretCache secretCache(SecretsManagerClient secretsClient) {
return new SecretCache(secretsClient);
}
}
```
### Service Layer
```java
@Service
public class SecretsService {
private final SecretCache cache;
public SecretsService(SecretCache cache) {
this.cache = cache;
}
public <T> T getSecretAsObject(String secretName, Class<T> type) {
String secretJson = cache.getSecretString(secretName);
return objectMapper.readValue(secretJson, type);
}
}
```
### Database Configuration
```java
@Configuration
public class DatabaseConfiguration {
@Bean
public DataSource dataSource(SecretsService secretsService) {
Map<String, String> credentials = secretsService.getSecretAsMap(
"prod/database/credentials");
HikariConfig config = new HikariConfig();
config.setJdbcUrl(credentials.get("url"));
config.setUsername(credentials.get("username"));
config.setPassword(credentials.get("password"));
return new HikariDataSource(config);
}
}
```
## Examples
### Database Credentials Structure
```json
{
"engine": "postgres",
"host": "mydb.us-east-1.rds.amazonaws.com",
"port": 5432,
"username": "admin",
"password": "MySecurePassword123!",
"dbname": "mydatabase",
"url": "jdbc:postgresql://mydb.us-east-1.rds.amazonaws.com:5432/mydatabase"
}
```
### API Keys Structure
```json
{
"api_key": "abcd1234-5678-90ef-ghij-klmnopqrstuv",
"api_secret": "MySecretKey123!",
"api_token": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
```
## Common Patterns
### Error Handling
```java
try {
String secret = secretsClient.getSecretValue(request).secretString();
} catch (SecretsManagerException e) {
if (e.awsErrorDetails().errorCode().equals("ResourceNotFoundException")) {
// Handle missing secret
}
throw e;
}
```
### Batch Operations
```java
List<String> secretNames = List.of("secret1", "secret2", "secret3");
Map<String, String> secrets = secretNames.stream()
.collect(Collectors.toMap(
Function.identity(),
name -> cache.getSecretString(name)
));
```
## Best Practices
1. **Secret Management**:
- Use descriptive secret names with hierarchical structure
- Implement versioning and rotation
- Add tags for organization and billing
2. **Caching**:
- Always use caching in production environments
- Configure appropriate TTL values based on secret sensitivity
- Monitor cache hit rates
3. **Security**:
- Never log secret values
- Use KMS encryption for sensitive secrets
- Implement least privilege IAM policies
- Enable CloudTrail logging
4. **Performance**:
- Reuse SecretsManagerClient instances
- Use async operations when appropriate
- Monitor API throttling limits
5. **Spring Boot Integration**:
- Use `@Value` annotations for secret names
- Implement proper exception handling
- Use configuration properties for secret names
## Testing Strategies
### Unit Testing
```java
@ExtendWith(MockitoExtension.class)
class SecretsServiceTest {
@Mock
private SecretCache cache;
@InjectMocks
private SecretsService secretsService;
@Test
void shouldGetSecret() {
when(cache.getSecretString("test-secret")).thenReturn("secret-value");
String result = secretsService.getSecret("test-secret");
assertEquals("secret-value", result);
}
}
```
### Integration Testing
```java
@SpringBootTest(classes = TestSecretsConfiguration.class)
class SecretsManagerIntegrationTest {
@Autowired
private SecretsService secretsService;
@Test
void shouldRetrieveSecret() {
String secret = secretsService.getSecret("test-secret");
assertNotNull(secret);
}
}
```
## Troubleshooting
### Common Issues
- **Access Denied**: Check IAM permissions
- **Resource Not Found**: Verify secret name and region
- **Decryption Failure**: Ensure KMS key permissions
- **Throttling**: Implement retry logic and backoff
### Debug Commands
```bash
# Check secret exists
aws secretsmanager describe-secret --secret-id my-secret
# List all secrets
aws secretsmanager list-secrets
# Get secret value (CLI)
aws secretsmanager get-secret-value --secret-id my-secret
```
## References
For detailed information and advanced patterns, see:
- [API Reference](./references/api-reference.md) - Complete API documentation
- [Caching Guide](./references/caching-guide.md) - Performance optimization strategies
- [Spring Boot Integration](./references/spring-boot-integration.md) - Complete Spring integration patterns
## Related Skills
- `aws-sdk-java-v2-core` - Core AWS SDK patterns and best practices
- `aws-sdk-java-v2-kms` - KMS encryption and key management
- `spring-boot-dependency-injection` - Spring dependency injection patterns

View File

@@ -0,0 +1,38 @@
import com.amazonaws.secretsmanager.caching.SecretCache;
import com.amazonaws.secretsmanager.caching.SecretCacheConfiguration;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class {{ConfigClass}} {
@Value("${aws.secrets.region}")
private String region;
@Bean
public SecretsManagerClient secretsManagerClient() {
return SecretsManagerClient.builder()
.region(Region.of(region))
.credentialsProvider(StaticCredentialsProvider.create(
AwsBasicCredentials.create(
"${aws.accessKeyId}",
"${aws.secretKey}"
)
))
.build();
}
@Bean
public SecretCache secretCache(SecretsManagerClient secretsClient) {
SecretCacheConfiguration config = SecretCacheConfiguration.builder()
.maxCacheSize(100)
.cacheItemTTL(3600000) // 1 hour
.build();
return new SecretCache(secretsClient, config);
}
}

View File

@@ -0,0 +1,126 @@
# AWS Secrets Manager API Reference
## Overview
AWS Secrets Manager provides a service to enable you to store, manage, and retrieve secrets with API version 2017-10-17.
## Core Classes
### SecretsManagerClient
- **Purpose**: Synchronous client for AWS Secrets Manager
- **Location**: `software.amazon.awssdk.services.secretsmanager.SecretsManagerClient`
- **Builder**: `SecretsManagerClient.builder()`
### SecretsManagerAsyncClient
- **Purpose**: Asynchronous client for AWS Secrets Manager
- **Location**: `software.amazon.awssdk.services.secretsmanager.SecretsManagerAsyncClient`
- **Builder**: `SecretsManagerAsyncClient.builder()`
## Configuration Classes
### SecretsManagerClientBuilder
- Methods:
- `region(Region region)` - Set AWS region
- `credentialsProvider(AwsCredentialsProvider credentialsProvider)` - Set credentials
- `build()` - Create client instance
### SecretsManagerServiceClientConfiguration
- Service client settings and configuration
## Request Types
### CreateSecretRequest
- **Fields**:
- `name(String name)` - Secret name (required)
- `secretString(String secretString)` - Secret value
- `secretBinary(SdkBytes secretBinary)` - Binary secret value
- `description(String description)` - Secret description
- `kmsKeyId(String kmsKeyId)` - KMS key for encryption
- `tags(List<Tag> tags)` - Tags for organization
### GetSecretValueRequest
- **Fields**:
- `secretId(String secretId)` - Secret name or ARN
- `versionId(String versionId)` - Specific version ID
- `versionStage(String versionStage)` - Version stage (e.g., "AWSCURRENT")
### UpdateSecretRequest
- **Fields**:
- `secretId(String secretId)` - Secret name or ARN
- `secretString(String secretString)` - New secret value
- `secretBinary(SdkBytes secretBinary)` - New binary secret value
- `kmsKeyId(String kmsKeyId)` - KMS key for encryption
### DeleteSecretRequest
- **Fields**:
- `secretId(String secretId)` - Secret name or ARN
- `recoveryWindowInDays(Long recoveryWindowInDays)` - Recovery period
- `forceDeleteWithoutRecovery(Boolean forceDeleteWithoutRecovery)` - Immediate deletion
### RotateSecretRequest
- **Fields**:
- `secretId(String secretId)` - Secret name or ARN
- `rotationLambdaArn(String rotationLambdaArn)` - Lambda ARN for rotation
- `rotationRules(RotationRulesType rotationRules)` - Rotation configuration
- `rotationSchedule(RotationScheduleType rotationSchedule)` - Schedule configuration
## Response Types
### CreateSecretResponse
- **Fields**:
- `arn()` - Secret ARN
- `name()` - Secret name
- `versionId()` - Version ID
### GetSecretValueResponse
- **Fields**:
- `arn()` - Secret ARN
- `name()` - Secret name
- `versionId()` - Version ID
- `secretString()` - Secret value as string
- `secretBinary()` - Secret value as binary
- `versionStages()` - Version stages
### UpdateSecretResponse
- **Fields**:
- `arn()` - Secret ARN
- `name()` - Secret name
- `versionId()` - New version ID
### DeleteSecretResponse
- **Fields**:
- `arn()` - Secret ARN
- `name()` - Secret name
- `deletionDate()` - Deletion date/time
### RotateSecretResponse
- **Fields**:
- `arn()` - Secret ARN
- `name()` - Secret name
- `versionId()` - New version ID
## Paginated Operations
### ListSecretsRequest
- **Fields**:
- `maxResults(Integer maxResults)` - Maximum results per page
- `nextToken(String nextToken)` - Token for next page
- `filter(String filter)` - Filter criteria
### ListSecretsResponse
- **Fields**:
- `secretList()` - List of secrets
- `nextToken()` - Token for next page
## Error Handling
### SecretsManagerException
- Common error codes:
- `ResourceNotFoundException` - Secret not found
- `InvalidParameterException` - Invalid parameters
- `MalformedPolicyDocumentException` - Invalid policy document
- `InternalServiceErrorException` - Internal service error
- `InvalidRequestException` - Invalid request
- `DecryptionFailure` - Decryption failed
- `ResourceExistsException` - Resource already exists
- `ResourceConflictException` - Resource conflict
- `ValidationException` - Validation failed

View File

@@ -0,0 +1,304 @@
# AWS Secrets Manager Caching Guide
## Overview
The AWS Secrets Manager Java caching client enables in-process caching of secrets for Java applications, reducing API calls and improving performance.
## Prerequisites
- Java 8+ development environment
- AWS account with Secrets Manager access
- Appropriate IAM permissions
## Installation
### Maven Dependency
```xml
<dependency>
<groupId>com.amazonaws.secretsmanager</groupId>
<artifactId>aws-secretsmanager-caching-java</artifactId>
<version>2.0.0</version> // Use the latest version compatible with sdk v2
</dependency>
```
### Gradle Dependency
```gradle
implementation 'com.amazonaws.secretsmanager:aws-secretsmanager-caching-java:2.0.0'
```
## Basic Usage
### Simple Cache Setup
```java
import com.amazonaws.secretsmanager.caching.SecretCache;
public class SimpleCacheExample {
private final SecretCache cache = new SecretCache();
public String getSecret(String secretId) {
return cache.getSecretString(secretId);
}
}
```
### Cache with Custom SecretsManagerClient
```java
import com.amazonaws.secretsmanager.caching.SecretCache;
import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient;
public class ClientAwareCacheExample {
private final SecretCache cache;
public ClientAwareCacheExample(SecretsManagerClient secretsClient) {
this.cache = new SecretCache(secretsClient);
}
public String getSecret(String secretId) {
return cache.getSecretString(secretId);
}
}
```
## Cache Configuration
### SecretCacheConfiguration
```java
import com.amazonaws.secretsmanager.caching.SecretCacheConfiguration;
public class ConfiguredCacheExample {
private final SecretCache cache;
public ConfiguredCacheExample(SecretsManagerClient secretsClient) {
SecretCacheConfiguration config = new SecretCacheConfiguration()
.withMaxCacheSize(1000) // Maximum number of cached secrets
.withCacheItemTTL(3600000); // 1 hour TTL in milliseconds
this.cache = new SecretCache(secretsClient, config);
}
}
```
### Configuration Options
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `maxCacheSize` | Integer | 1000 | Maximum number of cached secrets |
| `cacheItemTTL` | Long | 300000 (5 min) | Cache item TTL in milliseconds |
| `cacheSizeEvictionPercentage` | Integer | 10 | Percentage of items to evict when cache is full |
## Advanced Caching Patterns
### Multi-Layer Cache
```java
import com.amazonaws.secretsmanager.caching.SecretCache;
import java.util.concurrent.ConcurrentHashMap;
public class MultiLayerCache {
private final SecretCache secretsManagerCache;
private final ConcurrentHashMap<String, String> localCache;
private final long localCacheTtl = 30000; // 30 seconds
public MultiLayerCache(SecretsManagerClient secretsClient) {
this.secretsManagerCache = new SecretCache(secretsClient);
this.localCache = new ConcurrentHashMap<>();
}
public String getSecret(String secretId) {
// Check local cache first
String cached = localCache.get(secretId);
if (cached != null) {
return cached;
}
// Get from Secrets Manager cache
String secret = secretsManagerCache.getSecretString(secretId);
if (secret != null) {
localCache.put(secretId, secret);
}
return secret;
}
}
```
### Cache Statistics
```java
import com.amazonaws.secretsmanager.caching.SecretCache;
public class CacheStatsExample {
private final SecretCache cache;
public void demonstrateCacheStats() {
// Get cache statistics
long hitCount = cache.getHitCount();
long missCount = cache.getMissCount();
double hitRatio = cache.getHitRatio();
System.out.println("Cache Hit Ratio: " + hitRatio);
System.out.println("Hits: " + hitCount + ", Misses: " + missCount);
// Clear cache statistics
cache.clearCacheStats();
}
}
```
## Error Handling and Cache Management
### Cache Refresh Strategy
```java
import com.amazonaws.secretsmanager.caching.SecretCache;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class CacheRefreshManager {
private final SecretCache cache;
private final ScheduledExecutorService scheduler;
public CacheRefreshManager(SecretsManagerClient secretsClient) {
this.cache = new SecretCache(secretsClient);
this.scheduler = Executors.newScheduledThreadPool(1);
}
public void startRefreshSchedule() {
// Refresh cache every hour
scheduler.scheduleAtFixedRate(this::refreshCache, 1, 1, TimeUnit.HOURS);
}
private void refreshCache() {
System.out.println("Refreshing cache...");
cache.refresh();
}
public void shutdown() {
scheduler.shutdown();
}
}
```
### Fallback Mechanism
```java
import com.amazonaws.secretsmanager.caching.SecretCache;
public class FallbackCacheExample {
private final SecretCache cache;
private final SecretsManagerClient fallbackClient;
public FallbackCacheExample(SecretsManagerClient primaryClient, SecretsManagerClient fallbackClient) {
this.cache = new SecretCache(primaryClient);
this.fallbackClient = fallbackClient;
}
public String getSecretWithFallback(String secretId) {
try {
// Try cached value first
return cache.getSecretString(secretId);
} catch (Exception e) {
// Fallback to direct API call
return getSecretDirect(secretId);
}
}
private String getSecretDirect(String secretId) {
GetSecretValueRequest request = GetSecretValueRequest.builder()
.secretId(secretId)
.build();
return fallbackClient.getSecretValue(request).secretString();
}
}
```
## Performance Optimization
### Batch Secret Retrieval
```java
import com.amazonaws.secretsmanager.caching.SecretCache;
import java.util.List;
import java.util.ArrayList;
public class BatchSecretRetrieval {
private final SecretCache cache;
public List<String> getMultipleSecrets(List<String> secretIds) {
List<String> results = new ArrayList<>();
for (String secretId : secretIds) {
String secret = cache.getSecretString(secretId);
results.add(secret != null ? secret : "NOT_FOUND");
}
return results;
}
public Map<String, String> getSecretsAsMap(List<String> secretIds) {
Map<String, String> secretMap = new HashMap<>();
for (String secretId : secretIds) {
String secret = cache.getSecretString(secretId);
if (secret != null) {
secretMap.put(secretId, secret);
}
}
return secretMap;
}
}
```
## Monitoring and Debugging
### Cache Monitoring
```java
import com.amazonaws.secretsmanager.caching.SecretCache;
public class CacheMonitor {
private final SecretCache cache;
public void monitorCachePerformance() {
// Monitor cache hit rate
double hitRatio = cache.getHitRatio();
System.out.println("Cache Hit Ratio: " + hitRatio);
// Monitor cache size
long currentSize = cache.size();
System.out.println("Current Cache Size: " + currentSize);
// Monitor cache hits and misses
long hits = cache.getHitCount();
long misses = cache.getMissCount();
System.out.println("Cache Hits: " + hits + ", Misses: " + misses);
}
public void printCacheContents() {
// Note: SecretCache doesn't provide direct access to all cached items
// This is a security feature to prevent accidental exposure of secrets
System.out.println("Cache contents are protected and cannot be directly inspected");
}
}
```
## Best Practices
1. **Cache Size Configuration**:
- Adjust `maxCacheSize` based on available memory
- Monitor memory usage and adjust accordingly
- Consider using heap analysis tools
2. **TTL Configuration**:
- Balance between performance and freshness
- Shorter TTL for frequently changing secrets
- Longer TTL for stable secrets
3. **Error Handling**:
- Implement fallback mechanisms
- Handle cache misses gracefully
- Log errors without exposing sensitive information
4. **Security Considerations**:
- Never log secret values
- Use appropriate IAM permissions
- Consider encryption at rest for cached data
5. **Memory Management**:
- Monitor memory usage
- Consider cache eviction strategies
- Implement proper cleanup in shutdown hooks

View File

@@ -0,0 +1,535 @@
# AWS Secrets Manager Spring Boot Integration
## Overview
Integrate AWS Secrets Manager with Spring Boot applications using the caching library for optimal performance and security.
## Dependencies
### Required Dependencies
```xml
<!-- AWS Secrets Manager -->
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>secretsmanager</artifactId>
</dependency>
<!-- AWS Secrets Manager Caching -->
<dependency>
<groupId>com.amazonaws.secretsmanager</groupId>
<artifactId>aws-secretsmanager-caching-java</artifactId>
<version>2.0.0</version> // Use the latest version compatible with sdk v2
</dependency>
<!-- Spring Boot Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Jackson for JSON processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- Connection Pooling -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</dependency>
```
## Configuration Properties
### application.yml
```yaml
spring:
application:
name: aws-secrets-manager-app
datasource:
url: jdbc:postgresql://localhost:5432/mydb
username: ${db.username}
password: ${db.password}
hikari:
maximum-pool-size: 10
minimum-idle: 5
aws:
secrets:
region: us-east-1
# Secret names for different environments
database-credentials: prod/database/credentials
api-keys: prod/external-api/keys
redis-config: prod/redis/config
app:
external-api:
secret-name: prod/external/credentials
base-url: https://api.example.com
```
## Core Components
### SecretsManager Configuration
```java
import com.amazonaws.secretsmanager.caching.SecretCache;
import com.amazonaws.secretsmanager.caching.SecretCacheConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient;
@Configuration
public class SecretsManagerConfiguration {
@Value("${aws.secrets.region}")
private String region;
@Bean
public SecretsManagerClient secretsManagerClient() {
return SecretsManagerClient.builder()
.region(Region.of(region))
.build();
}
@Bean
public SecretCache secretCache(SecretsManagerClient secretsClient) {
SecretCacheConfiguration config = SecretCacheConfiguration.builder()
.maxCacheSize(100)
.cacheItemTTL(3600000) // 1 hour
.build();
return new SecretCache(secretsClient, config);
}
}
```
### Secrets Service
```java
import com.amazonaws.secretsmanager.caching.SecretCache;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.stereotype.Service;
import java.util.Map;
@Service
public class SecretsService {
private final SecretCache secretCache;
private final ObjectMapper objectMapper;
public SecretsService(SecretCache secretCache, ObjectMapper objectMapper) {
this.secretCache = secretCache;
this.objectMapper = objectMapper;
}
/**
* Get secret as string
*/
public String getSecret(String secretName) {
try {
return secretCache.getSecretString(secretName);
} catch (Exception e) {
throw new RuntimeException("Failed to retrieve secret: " + secretName, e);
}
}
/**
* Get secret as object of specified type
*/
public <T> T getSecretAsObject(String secretName, Class<T> type) {
try {
String secretJson = secretCache.getSecretString(secretName);
return objectMapper.readValue(secretJson, type);
} catch (Exception e) {
throw new RuntimeException("Failed to parse secret: " + secretName, e);
}
}
/**
* Get secret as Map
*/
public Map<String, String> getSecretAsMap(String secretName) {
try {
String secretJson = secretCache.getSecretString(secretName);
return objectMapper.readValue(secretJson,
new TypeReference<Map<String, String>>() {});
} catch (Exception e) {
throw new RuntimeException("Failed to parse secret map: " + secretName, e);
}
}
/**
* Get secret with fallback
*/
public String getSecretWithFallback(String secretName, String defaultValue) {
try {
String secret = secretCache.getSecretString(secretName);
return secret != null ? secret : defaultValue;
} catch (Exception e) {
return defaultValue;
}
}
}
```
## Database Configuration Integration
### Dynamic DataSource Configuration
```java
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
@Configuration
public class DatabaseConfiguration {
private final SecretsService secretsService;
@Value("${aws.secrets.database-credentials}")
private String dbSecretName;
public DatabaseConfiguration(SecretsService secretsService) {
this.secretsService = secretsService;
}
@Bean
public DataSource dataSource() {
Map<String, String> credentials = secretsService.getSecretAsMap(dbSecretName);
HikariConfig config = new HikariConfig();
config.setJdbcUrl(credentials.get("url"));
config.setUsername(credentials.get("username"));
config.setPassword(credentials.get("password"));
config.setMaximumPoolSize(10);
config.setMinimumIdle(5);
config.setConnectionTimeout(30000);
config.setIdleTimeout(600000);
config.setMaxLifetime(1800000);
config.setLeakDetectionThreshold(15000);
return new HikariDataSource(config);
}
}
```
### Configuration Properties with Secrets
```java
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "app")
public class AppProperties {
private final SecretsService secretsService;
@Value("${app.external-api.secret-name}")
private String apiSecretName;
public AppProperties(SecretsService secretsService) {
this.secretsService = secretsService;
}
private String apiKey;
public String getApiKey() {
if (apiKey == null) {
apiKey = secretsService.getSecret(apiSecretName);
}
return apiKey;
}
// Additional application properties
private String externalApiBaseUrl;
public String getExternalApiBaseUrl() {
return externalApiBaseUrl;
}
public void setExternalApiBaseUrl(String externalApiBaseUrl) {
this.externalApiBaseUrl = externalApiBaseUrl;
}
}
```
## Property Source Integration
### Custom Property Source
```java
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertySource;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.Map;
@Component
public class SecretsManagerPropertySource extends PropertySource<SecretsService> {
public static final String SECRETS_MANAGER_PROPERTY_SOURCE_NAME = "secretsManagerPropertySource";
private final SecretsService secretsService;
private final Environment environment;
public SecretsManagerPropertySource(SecretsService secretsService, Environment environment) {
super(SECRETS_MANAGER_PROPERTY_SOURCE_NAME, secretsService);
this.secretsService = secretsService;
this.environment = environment;
}
@PostConstruct
public void loadSecrets() {
// Load secrets specified in application.yml
String secretPrefix = "aws.secrets.";
environment.getPropertyNames().forEach(propertyName -> {
if (propertyName.startsWith(secretPrefix)) {
String secretName = environment.getProperty(propertyName);
String secretValue = secretsService.getSecret(secretName);
if (secretValue != null) {
// Add to property source (note: this is simplified)
// In practice, you'd need to work with PropertySources
}
}
});
}
@Override
public Object getProperty(String name) {
if (name.startsWith("aws.secret.")) {
String secretName = name.substring("aws.secret.".length());
return secretsService.getSecret(secretName);
}
return null;
}
}
```
## API Integration
### REST Client with Secrets
```java
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
@Service
public class ExternalApiClient {
private final SecretsService secretsService;
private final RestTemplate restTemplate;
private final AppProperties appProperties;
public ExternalApiClient(SecretsService secretsService,
RestTemplate restTemplate,
AppProperties appProperties) {
this.secretsService = secretsService;
this.restTemplate = restTemplate;
this.appProperties = appProperties;
}
public String callExternalApi(String endpoint) {
Map<String, String> apiCredentials = secretsService.getSecretAsMap(
appProperties.getExternalApiSecretName());
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Bearer " + apiCredentials.get("api_token"));
headers.set("X-API-Key", apiCredentials.get("api_key"));
headers.set("Content-Type", "application/json");
HttpEntity<String> entity = new HttpEntity<>(headers);
ResponseEntity<String> response = restTemplate.exchange(
endpoint,
HttpMethod.GET,
entity,
String.class);
return response.getBody();
}
}
```
### Configuration for REST Template
```java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RestTemplateConfiguration {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
```
## Security Configuration
### Security Setup
```java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfiguration {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/secrets/**").hasRole("ADMIN")
.anyRequest().permitAll()
)
.httpBasic()
.and()
.csrf().disable();
return http.build();
}
}
```
## Testing Configuration
### Test Configuration
```java
import com.amazonaws.secretsmanager.caching.SecretCache;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.mock.env.MockEnvironment;
import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient;
import software.amazon.awssdk.services.secretsmanager.model.GetSecretValueResponse;
import static org.mockito.Mockito.*;
@TestConfiguration
public class TestSecretsConfiguration {
@Bean
@Primary
public SecretsManagerClient secretsManagerClient() {
SecretsManagerClient mockClient = mock(SecretsManagerClient.class);
// Mock successful secret retrieval
when(mockClient.getSecretValue(any()))
.thenReturn(GetSecretValueResponse.builder()
.secretString("{\"username\":\"test\",\"password\":\"testpass\"}")
.build());
return mockClient;
}
@Bean
@Primary
public SecretCache secretCache(SecretsManagerClient mockClient) {
SecretCache mockCache = mock(SecretCache.class);
when(mockCache.getSecretString(anyString()))
.thenReturn("{\"username\":\"test\",\"password\":\"testpass\"}");
return mockCache;
}
@Bean
public MockEnvironment mockEnvironment() {
MockEnvironment env = new MockEnvironment();
env.setProperty("aws.secrets.region", "us-east-1");
env.setProperty("aws.secrets.database-credentials", "test-db-credentials");
return env;
}
}
```
### Unit Tests
```java
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;
@ExtendWith(MockitoExtension.class)
class SecretsServiceTest {
@Mock
private SecretCache secretCache;
@InjectMocks
private SecretsService secretsService;
@Test
void shouldGetSecret() {
String secretName = "test-secret";
String expectedValue = "secret-value";
when(secretCache.getSecretString(secretName))
.thenReturn(expectedValue);
String result = secretsService.getSecret(secretName);
assertEquals(expectedValue, result);
verify(secretCache).getSecretString(secretName);
}
@Test
void shouldGetSecretAsMap() throws Exception {
String secretName = "test-secret";
String secretJson = "{\"key\":\"value\"}";
Map<String, String> expectedMap = Map.of("key", "value");
when(secretCache.getSecretString(secretName))
.thenReturn(secretJson);
Map<String, String> result = secretsService.getSecretAsMap(secretName);
assertEquals(expectedMap, result);
}
}
```
## Best Practices
1. **Environment-Specific Configuration**:
- Use different secret names for development, staging, and production
- Implement proper environment variable management
- Use Spring profiles for environment-specific configurations
2. **Security Considerations**:
- Never log secret values
- Use appropriate IAM roles and policies
- Enable encryption in transit and at rest
- Implement proper access controls
3. **Performance Optimization**:
- Use caching for frequently accessed secrets
- Configure appropriate TTL values
- Monitor cache hit rates and adjust accordingly
- Use connection pooling for database connections
4. **Error Handling**:
- Implement fallback mechanisms for critical secrets
- Handle partial secret retrieval gracefully
- Provide meaningful error messages without exposing sensitive information
- Implement circuit breakers for external API calls
5. **Monitoring and Logging**:
- Monitor secret retrieval performance
- Track cache hit/miss ratios
- Log secret access patterns (without values)
- Set up alerts for abnormal secret access patterns