API Key 轮换导致生产事故?试试这两种无损方案
我之前带团队做业务时踩过一个坑:习惯性地先在后台撤销旧 Key,再更新环境变量并重新部署。结果在旧 Pod 还没被滚动替换完、新 Pod 还没拉起来的那个时间差里,所有请求全部失效。这种“看似零停机”的部署,在 Key 层面其实存在严重的竞态条件。
要解决这个问题,核心思路不是加快部署速度,而是要在过渡期内让系统同时兼容新旧两把 Key。
方案一:双 Key 并行模式 (Dual-Key Rotation)
这是最稳妥的逻辑。验证逻辑不再只检查一个 Key,而是同时校验「当前 Key」和「上一版本 Key」。当你执行轮换时,旧的 Current 变成 Previous 并设置一个过期时间(比如 300 秒),新的 Key 变成 Current。
这样即使部署流水线有延迟,旧的客户端在过渡期内依然能通过验证。
import time
import secrets
import hashlib
from dataclasses import dataclass
from typing import Optional@dataclass
class StoredKey:
key_hash: str
created_at: float
expires_at: Optional[float] = None
class DualKeyStore:
def __init__(self):
self.current: Optional[StoredKey] = None
self.previous: Optional[StoredKey] = None
@staticmethod
def _hash(raw: str) -> str:
return hashlib.sha256(raw.encode()).hexdigest()
def rotate(self, overlap_seconds: int = 300) -> str:
# 生成新 Key,仅返回一次原始值,数据库只存 Hash
raw = secrets.token_urlsafe(32)
new_key = StoredKey(key_hash=self._hash(raw), created_at=time.time())
if self.current:
# 给旧 Key 设置一个缓冲过期时间
self.current.expires_at = time.time() + overlap_seconds
self.previous = self.current
self.current = new_key
return raw
def is_valid(self, raw: str) -> bool:
h = self._hash(raw)
now = time.time()
if self.current and self.current.key_hash == h:
return True
if self.previous and self.previous.key_hash == h:
# 只有在过期时间未到时,旧 Key 才有效
if self.previous.expires_at is None or self.previous.expires_at > now:
return True
return False
方案二:版本化 Token (Versioned Key Tokens)
如果觉得维护两个 Key 麻烦,可以参考 Stripe 的做法:在 Token 结构里直接嵌入版本标识符。验证器根据 Token 里的 Version 去加载对应的签名密钥。轮换时,只需要增加一个新版本并给旧版本设限即可。
这种方式对开发者更友好,因为它把“密钥管理”变成了“版本管理”。
如果你正在处理涉及高频 API 调用的业务,别再用那种「先删再换」的原始方式了。