Secret management (passwords, API keys, user IDs) — Spring Boot best practices
1) Never hard-code or commit secrets
-
Don’t put secrets in code,
application.yml
, or sample configs. - Add to
.gitignore
:.env
,*.secrets.yml
,*.key
, keystores. - For local dev, load from env vars or a local .env (not committed).
# application.yml
spring:
datasource:
url: jdbc:postgresql://${DB_HOST:localhost}:5432/app
username: ${DB_USER}
password: ${DB_PASSWORD} # comes from env/secret manager; no inline defaults
2) Prefer a real secret manager (strongly recommended)
Pick one and standardize:
- HashiCorp Vault (+ Spring Cloud Vault)
- AWS Secrets Manager / Parameter Store
- GCP Secret Manager
- Azure Key Vault
- Kubernetes Secrets (with KMS/Envelope encryption enabled)
Example: Spring Cloud Vault (most portable)
Dependency
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-vault-config</artifactId>
</dependency>
Bootstrap config (application.yml)
spring:
cloud:
vault:
uri: http://vault:8200
authentication: approle
app-role:
role-id: ${VAULT_ROLE_ID}
secret-id: ${VAULT_SECRET_ID}
kv:
enabled: true
backend: secret
default-context: application
Vault path example: secret/application
holding spring.datasource.username
, spring.datasource.password
.
Supports rotation without redeploy (with @RefreshScope
or /actuator/refresh
if using Spring Cloud).
Example: AWS Secrets Manager (common on AWS)
Resolve secrets into env vars at container start (e.g., init script/sidecar), or use a library that maps secrets to Spring properties. Keep IAM policy scoped to only the secret ARNs you need.
Example: Kubernetes Secrets
Mount as files or env vars; prefer files for long values and to avoid accidental logging.
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-secret
key: password
3) Use typed binding & single source of truth
Bind secrets into @ConfigurationProperties (constructor-bound), not scattered @Value
strings.
@ConfigurationProperties(prefix = "app.db")
public record DbSecrets(String username, String password) {}
app:
db:
username: ${DB_USER}
password: ${DB_PASSWORD}
4) Limit blast radius (least privilege)
- Separate DB users per service; no shared superusers.
- Narrow IAM/roles to only the secrets/paths needed.
- Use network policies / SGs to limit who can talk to what.
5) Rotate, don’t “set and forget”
- Choose a rotation strategy (e.g., monthly/quarterly, or automate via the secret manager).
- For DB creds, use short-lived users or rotate passwords and reload datasource without downtime:
- With Spring Cloud + Vault: enable rotation &
@RefreshScope
. - Or implement a health-checked HikariCP re-init on password change.
- With Spring Cloud + Vault: enable rotation &
6) Don’t leak secrets in logs, errors, or metrics
- Never log request bodies or headers that can include tokens.
- Spring Boot sanitizes common keys in Actuator; extend it:
management:
endpoint:
env:
keys-to-sanitize: password,secret,key,token,.*credentials.*,.*pwd.*,apiKey
- Avoid printing
ConfigurationProperties
with.toString()
.
7) Secure Actuator & configuration endpoints
-
Expose only what you need; secure with auth & network controls.
management:
endpoints:
web:
exposure:
include: "health,info,metrics,prometheus"
server:
port: 8081
8) Protect secrets at rest & in transit
- Turn on encryption at rest for disks/volumes & DBs.
- Use TLS everywhere (service ↔ secret manager, app ↔ DB).
- For custom keys/certs, store in Java Keystore (JKS/PKCS12) managed by a secret manager or mounted as files with restricted permissions.
9) CI/CD handling
- Keep pipeline secrets in the CI system’s secret store (e.g., GitHub Actions Secrets, GitLab CI Variables).
- Inject at deploy time → environment of the container/task, not baked into images.
- Use multi-stage builds; ensure no
RUN echo SECRET >> file
remains in layers.
10) Testing strategy
- Use Testcontainers to provision throwaway DBs with ephemeral creds.
- For integration tests, load fake secrets via env or a test profile; never real keys.
11) Auditing & monitoring
- Turn on secret-manager audit logs (who read/rotated).
- Alert on anomalous secret reads, repeated failures, or access from unusual roles/regions.
Quick “good vs bad” cheatsheet
❌ Anti-patterns
- Secrets in
application.yml
or code - Sharing admin DB user across services
- Logging tokens/passwords
- Exposing
/actuator/env
publicly - Long-lived static credentials
✅ Good patterns
- Externalize via env/secret manager
- Typed binding with
@ConfigurationProperties
- Least-privilege roles & network controls
- Automated rotation & reload
- Extended sanitization & secure Actuator
Minimal starter template
Docker/K8s deployment env
spring:
datasource:
url: jdbc:mysql://${DB_HOST}:3306/app
username: ${DB_USER}
password: ${DB_PASSWORD}
app:
jwt:
issuer: ${JWT_ISSUER}
secret: ${JWT_SECRET} # Prefer KMS-managed or Vault-managed
Properties class
@ConfigurationProperties(prefix = "app.jwt")
public record JwtProps(String issuer, String secret) {}