site logo

Marico's space

告别 API 密钥:我们用自毁令牌重塑了身份认证

服务器技术 2026-04-23 22:24:27 14

译者按语

\n

在云原生和 DevSecOps 日益普及的今天,API 密钥的泄露与长期有效之间的矛盾已成为许多团队的安全痛点。本文深入探讨了 OathMesh 这一新兴项目,它通过强制性的短生命周期令牌和严格的验证机制,试图解决传统认证方式的顽疾。作为译者,我认为这篇文章不仅是对一个工具的介绍,更是对“零信任”架构下身份管理的一种深刻反思。希望这篇译文能为你的安全架构设计带来新的灵感。

\n\n

用自毁令牌取代 API 密钥

\n

你的 CI 流水线里藏着一个秘密。它已经在你的环境变量里躺了两年,你并不清楚谁拥有访问它的权限。想要旋转它?那得协调三个团队。于是,你选择了不去管它。

\n

这并非流程的失败,而是 API 密钥的设计初衷:它们被设计为长期存在的字符串,因为不得不如此。

\n

我们是 Moustafa Mahmoud Atta 和 Abd El-Sabour Ashraf,我们构建了 OathMesh 来改变这一默认设定。

\n

在 OathMesh 中,每一次机器调用都会获得一个经过加密签名的令牌,它被限定在单一操作范围内,并且在 ≤ 5 分钟后自动失效。

\n\n

两行 HTTP 代码的核心理念

\n

在 OathMesh 出现之前,你的请求头可能是这样的:

\nAuthorization: Bearer abc123xyz_still_valid_since_2022\n

而现在,它变成了:

\nAuthorization: OathMesh eyJhbGciOiJFZERTQSIsInR5cCI6Im9tK2p3dCJ9...\n

└── 300 秒后过期。强制执行。非可选。

\n

如果令牌泄露?当攻击者试图使用它时,它已经是一堆废数据了。

\n\n

诚实的对比

\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
特性API 密钥典型 JWT短生命周期 JWT + jtiOathMesh
生命周期♾️ 永久小时 - 天5-15 分钟 (需配置)⏱️ ≤ 300 秒。强制执行。
加密算法❌ 仅字符串HS256 / RS256RS256 / ES256✅ 仅限 Ed25519
重放保护✅ jti 黑名单 (需自行实现)✅ 内置且强制
操作范围限制⚠️ 自定义,可选⚠️ 自定义,可选✅ act 字段强制要求
策略引擎✅ Pkl 规则,热重载
审计日志⚠️ 自建⚠️ 自建✅ 每次允许/拒绝记录,NDJSON
泄露影响范围💀 永久🩸 小时🟡 分钟🟢 ≤ 5 分钟。上限。
轮换😰 手动 + 协调⚠️ 视情况而定⚠️ 视情况而定✅ 设计即自动过期
\n\n

诚实的看法:你可以通过短生命周期 JWT + jti 黑名单的方式接近 OathMesh 的效果。但 OathMesh 提供的不仅仅是这些,它是一个有立场的封装:强制的 TTL 执行(无法禁用)、强制的 act 范围限制(而非可选)、内置的策略引擎以及开箱即用的完整审计轨迹,无需 DIY。

\n\n

如果你已经在运行 SPIFFE/SPIRE 或云工作负载身份(如 AWS IRSA, GCP WI)?那很好,它们非常适合 Kubernetes 原生环境。但 OathMesh 是为那些希望拥有这种安全模型,却不想背负完整服务网格(Service Mesh)开销的团队准备的。如果你想要更简单,那就继续用 API 密钥;如果你想要更安全,请继续读下去。

\n\n

真实场景:GitHub Actions → 你的部署 API

\n

CI 作业不再存储任何秘密。它请求一个 300 秒生命周期的令牌,使用它,然后它自动消失。即使有人从你的日志中捕获了它,他们也一无所获。

\n\n

验证机制详解:14 步,失败即关闭

\n

Fail-closed(失败即关闭)意味着:任何单一步骤失败,请求立即被拒绝。没有部分有效状态,没有回退机制,直接返回 401。

\n\n

验证流程如下:

\n
    \n
  1. 有效的 JWS 紧凑结构(3 段)
  2. \n
  3. Header: typ = om+jwt, alg = EdDSA
  4. \n
  5. Payload 解码,提取 iss
  6. \n
  7. iss 在可信发行者列表中
  8. \n
  9. 加载 JWKS(内存缓存)
  10. \n
  11. Ed25519 签名验证 —— 杜绝算法混淆
  12. \n
  13. 签名后再次验证 iss
  14. \n
  15. exp 在将来(±10 秒时钟漂移容忍度)
  16. \n
  17. iat 不在将来
  18. \n
  19. aud 完全匹配
  20. \n
  21. 所有必需声明存在:sub, act, jti
  22. \n
  23. 检查请求哈希绑定(如果存在)
  24. \n
  25. jti 检查重放缓存 —— 已存在则拒绝
  26. \n
  27. 策略评估 —— 发出审计事件
  28. \n
\n\n

第 6 步和第 13 步是重头戏。没有算法混淆,没有重放攻击,没有例外。

\n\n

网关模式:保护无法修改的服务

\n

已经有一些无法修改的 API?将 OathMesh 作为反向代理运行在它们前面。

\n

你的上游服务将收到干净、预验证的身份头。无需任何代码更改。

\n\n

5 行代码集成到你的技术栈

\n\n

Go (chi):

\n
r.Use(middleware.OathMeshMiddleware(cfg))\n\n// 在你的 handler 中获得完全类型的调用者上下文:\ncaller := middleware.CallerFrom(r.Context())\n// caller.Principal.Subject → "agent://ci/deploy-bot"\n// caller.Action → "deploy"\n// caller.TokenID → 此调用的唯一 jti
\n\n

Python (FastAPI):

\n
from oathmesh import verify_token, OathMeshError\n\n@app.post("/deploy")\nasync def deploy(request: Request):\n  try:\n    caller = verify_token(request.headers["authorization"], config)\n  except OathMeshError as e:\n    raise HTTPException(status_code=401, detail=str(e))\n  return {"deployed_by": caller.principal.subject}
\n\n

Next.js (App Router):

\n
import { withOathMesh } from '@oathmesh/oathmesh/next';\n\nconst oathmesh = withOathMesh({ audience, trustedIssuers });\n\nexport async function POST(request: NextRequest) {\n  const { caller, error } = await oathmesh(request);\n  if (error) return error; // 类型安全的 401 — 缺失、无效、过期、重放\n  return NextResponse.json({ subject: caller.principal.subject });\n}
\n\n

3 条命令体验它

\n

OathMesh 架构图

\n\n
git clone https://github.com/oathmesh/oathmesh.git && cd oathmesh\ndocker-compose up -d\n\n# 签发一个令牌(300 秒 = 最大值,由发行者强制执行)\nTOKEN=$(docker compose exec oathmesh ./bin/oathmesh mint \\\n --sub "agent://repo/acme/deploy-bot" \\\n --aud "https://inventory.internal" \\\n --act "deploy" \\\n --ttl 300 \\\n --quiet)\n\ncurl -H "Authorization: OathMesh $TOKEN\