第12章 短凭据与密钥轮换解决不同问题
凭据寿命与密钥寿命不能混为一谈
访问令牌三十秒到期,并不意味着签名密钥也应该三十秒更换。令牌寿命限制单个凭据被盗后的使用窗口,密钥轮换限制签名材料长期暴露并支持算法与运营变更。轮换若处理不当,反而会让所有在途请求突然失败,或让已经泄漏的旧键无限存活。
标准轮换通常经历发布新公钥、开始用新私钥签发、保留旧公钥验证在途令牌、等待最长有效期及容差结束、最后退休旧公钥。若私钥已经确认泄漏,就不能按正常重叠窗口继续接受旧签名;需要事故撤销策略,并评估现存令牌和审批是否应整体失效。
实验 /admin/rotate 的 Retire=false 创建新活动键并保留旧公钥,旧令牌继续有效;Retire=true 清空旧键集,已有令牌立即因找不到 kid 被拒绝。新登录令牌随后正常使用。这个实验验证密钥集合变化和接收方反应,不包含 KMS、HSM、硬件保护或分布式密钥发布。
JWKS 缓存的工程取舍
实验每次验证都从固定身份服务拉取 JWKS,便于确定地观察轮换和故障。生产不应照抄这一性能策略。合理实现会缓存公钥,尊重受控缓存时间,对未知 kid 做有限刷新,并限制刷新并发和频率。随机 kid 攻击不应导致每个请求都访问身份平台。
缓存同时影响紧急退休速度。若业务服务缓存旧公钥一小时,即使签发方已删除旧键,它仍可能接受旧签名。你需要定义紧急缓存失效通道、最大 TTL 和撤权补充机制。高风险操作可以在线查状态,而一般读取使用短缓存;不同动作允许不同一致性,但规则应明确且可观测。
失败案例是团队给新键使用了与旧键相同的 kid。不同实例缓存不同公钥,部分请求成功,部分随机失败。另一个案例是退休旧键时没有考虑最长令牌有效期,使移动客户端集体掉线。kid 应稳定识别具体密钥材料,轮换演练应覆盖多实例缓存和时钟偏差,而非只在签发方看到“生成成功”。
服务认证材料也要轮换
代理访问身份服务的客户端凭据与签发 JWT 的私钥不同。前者证明哪个服务在请求,后者证明谁签发了令牌。生产中可使用受控客户端密钥、private_key_jwt、mTLS 或平台工作负载身份。具体选择应结合部署环境和身份产品,不要把公开示例密钥当成可上线的机密管理方案。
本书把开发服务密钥直接写在代码中,是为了让下载包无需私有配置就能运行,且所有监听都限制在环回。它没有实现机密注入、双密钥重叠和自动刷新。这些是生产化工作,需要单独测试泄漏响应、回滚和失效传播。Keycloak 容器的 client secret 同样公开,仅用于隔离实验。
实验与练习
运行 go test -race -run TestKeyRotationAndDependencyOutage -v ./lab,观察重叠、退休、新令牌恢复和身份服务关闭四个阶段。确认旧令牌在正常重叠时有效,退休后失败;不要只测新令牌能用。
练习:为你的服务写一份正常轮换与紧急泄漏轮换的时序。列出最长令牌寿命、时钟容差、JWKS 缓存、网关缓存和离线执行器。计算最早可以安全退休旧键的时刻,再说明紧急事件中如何主动缩短暴露窗口以及可能造成的业务中断。
正常轮换与事故轮换的验收差异
正常轮换最重要的是连续性:旧令牌在合法生命周期内仍可验证,新令牌使用新键,多实例逐步更新不会随机失败。事故轮换最重要的是停止信任:旧键在最大承诺窗口内不再被接受,缓存能够失效,相关令牌与任务被识别。把两者使用同一套“等待一天再删除”的流程,会让事故处理过慢。
演练时应记录每个接收者实际停止接受旧键的时刻,而不是只记录身份平台删除键的时刻。接收者可能是网关、业务服务、异步执行器和离线报表。通过向各入口提交旧签名样本,才能测出真实传播;配置页面显示成功只是控制面证据。
也不要在代码日志中输出私钥来帮助排障。公钥指纹、kid 和活动状态足以定位大多数轮换问题。私钥应只在受控签名组件中使用,备份和恢复需要独立权限。教学实验每次启动随机生成私钥,重启后全部旧令牌失效,这是简化生命周期而不是生产密钥持久化方案。