项目概述
本项目实现完整的 API 密钥生命周期:以“前缀 + 秘密”格式签发,只存储前缀与慢哈希,通过前缀索引定位后校验一个候选,并按作用域、过期、带重叠期的轮换与用量计量管理密钥。设计前提是数据库终会泄露,因此转储不能暴露可用的凭据。
架构与关键决策
密钥行保存前缀、整串的 argon2 哈希、作用域、过期时间与版本;校验时按索引前缀取一行再做一次哈希,成本是一次请求一次哈希而非每个存量密钥一次。作用域按子集判断,使鉴权成为一次集合比较;轮换是一次状态变更——旧密钥进入宽限期并签发同作用域的新密钥,让客户端自行迁移而不必在发布时刻切换。
实施与测试
断言被吊销的密钥被拒、作用域被正确约束、轮换在宽限期内仍接受旧密钥且超期后失效、配额耗尽返回 429,并验证明文从不落库。
限制与后续
- 密钥为不透明字符串,不含可离线解析的元数据。
- 轮换为手动操作,未做定时调度。
- 配额为每密钥统一计价,尚无按端点的成本权重。
代码示例
pnpm init
pnpm add pg @node-rs/argon2 express
pnpm add -D typescript tsx vitest
api-key-service/
├── src/issue.ts
├── src/verify.ts
├── src/rotate.ts
├── src/usage.ts
├── tests/keys.test.ts
└── package.json
const secret = base64url(crypto.randomBytes(24));
const full = 'sk_' + prefix + '_' + secret;
await db.insert({ prefix, hash: await argon2.hash(full), scopes, expiresAt });
const row = await db.findByPrefix(prefix);
if (!row || !(await argon2.verify(row.hash, presented))) return null;
if (!scopes.every((s) => key.scopes.includes(s))) return res.status(403).end();
await db.update(oldKeyId, { expiresAt: now + graceMs });
return issueKey(sameScopes);
await redis.incr('quota:' + keyId);
pnpm vitest run