← 返回资讯

API Key 安全管理:从存储到轮换的完整实践

2026-06-22

API key 泄露是大模型接入中最高频的安全事故:硬编码进代码提交到 GitHub、写入前端 JS 被浏览器抓包、截图里无意暴露……任何一种都可能导致账单暴增或服务被滥用。本文从存储到应急,给出一套可落地的 key 管理规范。

我见过最惨的一次是团队里一个实习生把 key 写死在 demo 脚本里,图省事直接 git push 到了公开仓库,人还没下班账单就多了小两千。别觉得这是小概率事件——GitHub 上专门有爬虫 7×24 小时扫描新提交里的 sk- AKIA 这类特征字符串,从你 push 到有人拿着你的 key 发请求,中间可能就几分钟,比你反应过来还快。所以后面这些规矩不是形式主义,是真会决定你这个月账单是三位数还是五位数。

绝对禁止的行为

  • sk-xxx 字面量写入任何代码文件(即使是”临时测试”)
  • 把 key 放入 .env 后未将 .env 加入 .gitignore
  • 在前端(浏览器侧)代码中直接使用 key
  • 通过 Slack / 钉钉 / 微信明文传递 key
  • 在截图、录屏、文档中暴露 key(即使是内部文档)

这几条里最容易踩的其实是第二条:很多人以为 key 放进 .env 就安全了,结果 .env 本身没进 .gitignore,第一次 git add . 就把它连着代码一起提交了。更隐蔽的坑是:就算你后来补上了 .gitignore 并删掉了 .env 文件,这个文件依然完整地躺在 git 历史记录里——只要有人 git log -p 翻一下提交历史,或者 clone 下你的仓库跑一遍 git log --all --full-history -- .env,key 照样能被扒出来。这也是为什么后面「泄露应急处理」里必须专门讲 git filter-repo 清历史,光删文件不够。

推荐存储方式

场景推荐方式说明
本地开发.env 文件 + .gitignore绝不提交,每人独立文件
CI/CD平台 Secret(GitHub Actions Secrets 等)注入为环境变量,日志不可见
容器/服务环境变量注入 or K8s Secret避免写入镜像层
生产(中大型)Vault / AWS Secrets Manager / 阿里云 KMS集中管理、审计、自动轮换
团队共享OneAPI/NewAPI 中转 + 独立令牌成员用子令牌,主 key 集中管理

怎么在这几种里挑,看团队规模和预算就行,不用一上来就上重型方案:

  • 个人项目/小团队 MVP 阶段.env + .gitignore 完全够用,上 Vault 属于过度设计,维护成本比它防的风险还高。
  • 有 CI/CD 流水线:直接用平台自带的 Secret 功能(GitHub Actions Secrets、GitLab CI Variables),这些平台会自动在日志输出里把 key 值替换成 ***,比自己在脚本里 echo 环境变量安全得多。
  • 容器化部署但没有专门的密钥团队:环境变量注入 or K8s Secret 就行,重点是别把 key 写进 Dockerfile 或打进镜像层——镜像一旦推到公共仓库或者被人 docker history 一下,历史层里的内容照样能扒出来。
  • 十人以上团队、多套环境、有审计合规要求:这时候才值得上 Vault / AWS Secrets Manager / 阿里云 KMS,图的是集中管理、访问审计、自动轮换这几个能力,不是单纯为了”高级”。

本地开发规范

# .env(不提交)
OPENAI_API_KEY=sk-xxx
OPENAI_BASE_URL=https://api.lidayun.com/v1

# .gitignore(必须包含)
.env
.env.local
.env.*.local

Python 读取:

import os
from dotenv import load_dotenv

load_dotenv()  # 读取 .env
api_key = os.environ["OPENAI_API_KEY"]  # 不存在时直接报错,而非返回 None

Node.js/Next.js 读取环境变量类似,框架通常内置 .env.local 支持。

这段代码里有个细节值得多说一句:为什么用 os.environ["OPENAI_API_KEY"] 而不是更常见的 os.environ.get("OPENAI_API_KEY")?两者的区别是——后者在 key 不存在时会静默返回 None,你的代码可能会拿着 None 去初始化 SDK 客户端,一直跑到真正发请求那一刻才报一个语焉不详的 AuthenticationError,排查起来还得回溯到底是哪一层没读到值;而 os.environ["OPENAI_API_KEY"] 在 key 缺失时直接抛 KeyError,第一时间就能定位是环境变量没配对,这叫”快速失败”(fail fast),调试成本天差地别。

另外容器化部署时要留意一个反直觉的点:环境变量看似”隐蔽”,其实在 Linux 里,同一台主机上 root 用户或者拥有相同 namespace 权限的进程,是可以直接读 /proc/<pid>/environ 拿到目标进程的全部环境变量的。所以「用环境变量注入」防的是代码泄露(key 不出现在源码和镜像里),防不住主机层面被入侵——如果你的服务器本身权限管理松散,环境变量一样能被读走。这也是为什么金融、医疗这类强合规场景会进一步要求用 Vault 这类工具做”运行时按需拉取、用完即焚”,而不是长期驻留在环境变量里。

权限最小化

  • 为不同项目生成独立 key,互不复用
  • 在平台控制台给 key 设置月度用量上限(spending limit),防止意外爆单
  • 开发/测试环境用低额度 key,生产用独立高额度 key
  • 只给 key 必要的模型权限(部分平台支持 key 级别的模型范围)

这里顺带说个很多人会踩的坑:给 key 设置了用量上限之后,一旦触发限制,请求不会报”额度不足”这种直白的错,而是返回 429 Too Many Requests 或者 insufficient_quota,跟真正的限流错误长得几乎一样。排查思路是先看平台控制台的用量面板,确认是不是月度上限打满了,而不是一上来就以为自己被限流了去调重试退避参数——调了也没用,额度没了重试多少次都是 429。

定期轮换

建议每 90 天轮换一次 key,步骤:

  1. 在平台控制台生成新 key
  2. 在密钥管理服务中更新新 key
  3. 验证新 key 可用(发一次测试请求)
  4. 吊销旧 key

单 key 场景按这个流程走没问题,但如果你的服务是多实例部署、不能停机的,这个流程会有一个几秒到几分钟的空窗期:你在第 4 步吊销旧 key 之前,如果有实例还没重启加载新配置,用的还是旧 key,吊销瞬间这些实例的请求会全部 401。更稳妥的做法是”双 key 并行过渡”:

  1. 生成新 key,但先不吊销旧 key,两者同时有效
  2. 把新 key 灰度发布到部分实例,观察一段时间(比如 30 分钟)确认调用正常
  3. 全量切换所有实例到新 key
  4. 确认所有实例的日志里再也看不到旧 key 的调用记录后,再吊销旧 key

这套流程本质上是「蓝绿部署」思路搬到密钥轮换上,多花几步换来的是零停机,线上服务优先级高的话这几步不能省。

泄露应急处理

发现 key 泄露后,第一步立即吊销,而非先调查原因:

  1. 登录 API 平台控制台 → 吊销泄露的 key(30秒内完成)
  2. 生成新 key,按规范注入
  3. 查看账单与用量日志,确认是否已被滥用
  4. 如有异常消费,联系平台客服申诉
  5. 排查泄露路径(git history、日志、截图等),修复根因

使用 git filter-repo 或 BFG Repo Cleaner 清除历史提交中的 key:

# 从 git 历史中删除含 key 的文件(谨慎操作,会重写历史)
git filter-repo --path .env --invert-paths

git filter-repo 会重写所有涉及该文件的提交哈希,等于改写了整个仓库的历史,操作前务必:先给团队所有成员发通知(因为他们本地的分支会跟远端历史”分叉”,之后得重新 clone 或者强制同步);备份一份仓库;确认没有其他人正在这个仓库上提交。清理完历史后这个 key 本身也必须视为已泄露而吊销,改历史只是清理证据、防止后来者顺手扒出来,不能替代吊销这一步——已经被爬虫抓走的 key,你改不改历史它都已经泄露了。

被动等平台或 GitHub 扫描出来终究是慢了一步,更好的做法是主动防线前移:在 CI 流程里加一道 secret scanning,用 gitleaks 或 TruffleHog 这类开源工具,在每次 push 或 PR 时自动扫描 diff 里有没有符合 key 特征的字符串,一旦命中直接拦截合并,而不是等提交进了主分支再补救。落地起来也不复杂,比如在 GitHub Actions 里加一步:

- name: Scan for leaked secrets
  uses: gitleaks/gitleaks-action@v2

配合前面提到的 .gitignore,这一层扫描能兜住”忘记加 gitignore”或者”key 直接写死在代码里”这两类最常见的疏忽,成本几乎为零,强烈建议团队项目都加上。

另外别只盯着 git 提交,日志系统是另一个高发泄露口:如果你的代码在报错时把完整的 request headers(包含 Authorization: Bearer sk-xxx)打进了日志,而日志又同步到了某个第三方监控平台或者堆到了公开可访问的 ELK 面板,这也是一次实打实的泄露,而且往往比 git 泄露更难发现——没人会想到去翻日志。建议在日志打印前对 key 做脱敏处理,只保留前 6 位和后 4 位,中间用 *** 代替,比如 sk-proj***7f2a,既方便你自己核对是哪个 key 出的问题,又不会完整暴露。

常见问题

Q:前端应用如何安全调用大模型 API? 前端绝不能持有 key。正确做法:前端请求自己的后端接口,后端持有 key 并调用大模型,可做鉴权、限速、内容过滤后再返回给前端。这个后端中转层业内一般叫 BFF(Backend for Frontend),它的价值不只是”藏 key”,还包括:统一给不同前端(Web/小程序/App)适配返回格式、在服务端做用户级别的调用频率限制(防止某个用户脚本刷爆你的额度)、以及把敏感的 prompt 拼装逻辑放在服务端不暴露给前端调试工具。如果你嫌自己写 BFF 麻烦,也可以直接用 OneAPI/NewAPI 这类中转网关顶上这一层,效果类似。

Q:怎么第一时间发现账单异常,而不是等月底账单出来才傻眼? 大部分平台的控制台都支持设置用量告警阈值,比如用量达到月度额度的 50%/80%/100% 时发邮件或 webhook 通知,这个功能开着基本不花时间成本,一定要开。更进一步,如果你的调用量本身有规律(比如工作日白天流量高、夜里几乎没有),可以自己写个简单的定时任务,每小时拉一次用量 API,跟历史同期比对,一旦某小时用量是平时的 5 倍以上就报警——这种异常往往比等额度耗尽或者账单出来快得多,能在损失几十块钱的时候就发现问题,而不是几千块。

Q:GitHub 能自动检测 key 泄露吗? OpenAI 等主流平台已与 GitHub 合作 Secret Scanning,一旦检测到 key 格式会自动通知平台和用户,部分平台会立即自动吊销。但这是最后一道防线,不能替代主动防护。

Q:团队多人开发如何避免 key 混用? 推荐部署 OneAPI/NewAPI 中转:主 key 集中管理,成员各自领取子令牌,令牌可独立限速、审计和吊销,主 key 永不暴露给成员。


延伸阅读:大模型 API 接入完全指南 · 接入教程 Hub · OneAPI/NewAPI 自建中转接入 · 改 base_url 切换 OpenAI 兼容接口