Secret 与变量

AuraBoot 的配置是分层的,每一层覆盖下层。理解层级顺序至关重要:紧急补丁注入到了错误的层,要么完全不生效,要么泄漏进受 git 跟踪的文件。

三层模型

来源优先级典型用途
1. 程序参数--spring.data.redis.host=...最高一次性 override,生产 launcher
2. 环境变量DATABASE_PASSWORDSPRING_DATA_REDIS_HOST容器部署、CI
3. 外部 vault通过 spring.config.import 接入 Hashicorp Vault / 云 KMS三层中最低(仍高于 application.yml长期 secret、轮换

打包后的 application.yml 只包含不敏感的默认值;它绝不应该携带真实密码或 API key。

敏感字段

下列字段绝不能提交,必须来自第 2 层或第 3 层:

字段说明
spring.datasource.passwordPostgreSQL 主库凭据
spring.data.redis.host / password企业版启动时强依赖 Redis
security.jwt.secret签发平台 JWT;轮换会让所有会话失效
aura.storage.minio.access-key / secret-key对象存储
aura.mq.kafka.bootstrap-servers 凭据bootstrap host 本身打日志没问题;SASL secret 不行
Connector 凭据(cr_csp_connector_pid resolver)第三方 API key、OAuth token
Webhook 签名 secretWebhooks
LLM 提供商 keyOpenAI / Claude / 本地模型网关 token

何时使用

  • 引导一个全新环境:以环境变量为主,便于迁移。
  • 生产且需要频繁轮换:绑定外部 vault,绝不硬编码。
  • 本地开发:用一个 gitignore 的 .env 文件加一个把它翻译成程序参数的 launcher。

工作原理

Spring Boot 按声明的优先级顺序解析配置。AuraBoot 在此基础上扩展了一个 connector 凭据 resolver,会按 cr_csp_connector_pid 查 bearer / basic / api-key 负载,并把它们注入出站请求,而不会把明文落到 DSL 里。

# application.yml(受提交跟踪,且不含 secret)
spring:
  config:
    import:
      - "optional:vault://secret/auraboot/${SPRING_PROFILES_ACTIVE}"
  datasource:
    url: "${DATABASE_URL:jdbc:postgresql://localhost:5432/aura_boot?charSet=UTF8}"
    username: "${DATABASE_USERNAME:auraboot}"
    password: "${DATABASE_PASSWORD:}"   # 生产必填,无默认值
  data:
    redis:
      host: "${REDIS_HOST:localhost}"   # 启用 Redis 时由 SPRING_DATA_REDIS_HOST 注入
      port: "${REDIS_PORT:6379}"

security:
  jwt:
    secret: "${JWT_SECRET}"   # 必填

一次性 override(例如事故响应时),用程序参数传入,避免泄漏到每一个子 shell 的进程环境里:

java -jar platform.jar \
  --spring.profiles.active=prod \
  --spring.data.redis.host=10.0.3.7 \
  --spring.datasource.hikari.maximum-pool-size=50

轮换

# 1. 生成新密钥;把当前密钥降级为「上一把」,保证旧 token 仍可校验
NEW_SECRET="$(openssl rand -base64 64)"
# JWT_PREVIOUS_SECRET=$JWT_SECRET, JWT_PREVIOUS_KID=$JWT_KID
# JWT_SECRET=$NEW_SECRET,        JWT_KID=key-$(date +%Y%m%d)

# 2. 滚动重启每个节点,让新配置生效(平台未启用 Spring Cloud /actuator/refresh)
#    新签发的 token 用新密钥,旧 token 仍由 previous-secret 校验

# 3. 验证新 JWT 可签发:用新 token 调一个受保护端点
curl -sS "$AURA_API_URL/api/auth/me" \
  -H "Authorization: Bearer $NEW_TOKEN"

# 4. 过一个 token 过期周期(默认 86400s)后,移除 JWT_PREVIOUS_* 环境变量

平台用 security.jwt.previous-secret / previous-kid 支持双密钥并存:滚动重启期间旧 token 仍可校验,无需让所有活跃会话立即失效。一个过期周期后下线旧密钥即可。安排在维护窗口里执行,或者借助具备会话亲和性的负载均衡器逐节点滚动。

验证

  • 在仓库上 git grep 找不到任何已提交的 secret(pg_passwordjwt.secret:、明文 bearer token)。
  • /actuator/configprops(只有 admin 可访问)对敏感字段清单中的每一项都展示为掩码。
  • 服务端 INFO 级日志不回显 secret;结构化日志过滤器会掩码 passwordsecrettokenapiKey
  • 轮换后的 secret 在每个节点滚动重启后生效;旧凭据被拒。
  • CI 流水线从 CI vault 集成中取 secret,绝不从明文的仓库变量里取。

相关