AI 网关统一鉴权与多租户 Key 管理
AI 网关的鉴权层解决了一个核心矛盾:上游服务商的密钥需要集中管控,下游的多个团队、应用、客户又需要独立的访问凭证。统一鉴权层让两者互不影响,同时提供细粒度的权限与配额控制。
鉴权层的整体架构
下游调用方
├── 应用 A(key: sk-app-a-xxx)
├── 应用 B(key: sk-app-b-xxx)
└── 客户 C(key: sk-cust-c-xxx)
│
▼
AI 网关鉴权层
├── 验证 key 合法性
├── 检查权限(可用模型、功能)
├── 检查配额(token 余额、速率限制)
└── 映射到上游渠道(屏蔽真实密钥)
│
▼
上游服务商密钥(集中存储,加密)
├── OpenAI: sk-openai-xxx(对外不可见)
├── Anthropic: sk-ant-xxx(对外不可见)
└── 阿里通义: key-qwen-xxx(对外不可见)
核心原则:下游只持有网关 key,从不直接接触上游密钥。
这一层看着简单,真正落地时容易在两个地方栽跟头。第一是”图省事”——刚开始只有一两个内部脚本在用网关,很多人懒得建鉴权层,直接把上游 key 抄给同事用,等团队扩到五六个应用之后才发现,谁在跑什么模型、跑了多少量完全没法追溯,一旦某个 key 泄露只能全员换密钥,代价极大。第二是”过度设计”——一上来就想把 OAuth2、JWT、mTLS 全套上,结果自己团队三五个人天天卡在鉴权调试上,反而拖慢了业务接入。经验是:内部工具期用简单的”网关 key + Redis 记账”就够,等外部客户接入、需要审计合规的时候再加复杂度,不要提前一次性做满。
网关 key 格式怎么选:不透明串 vs 自带信息的复合 key
大部分聚合网关(包括 OneAPI/NewAPI)用的是”不透明串”方案:key 本身就是一串随机字符(如 sk- 前缀 + 48 位随机字符),网关收到后拿这串去数据库或缓存里查对应的权限、配额记录,key 本身不携带任何业务信息。这种方案的好处是简单可控——想吊销就删数据库记录,想改权限直接改记录,key 值永远不用变。
另一种是把 key 设计成 JWT 之类自带信息的令牌,权限、配额上限、有效期都编码在 token 里,网关拿到后本地解码验签就能拿到权限信息,不用查库。这种方案在”要不要吊销”这件事上很麻烦:JWT 一旦签发,在过期之前理论上是有效的,除非你额外维护一个吊销黑名单(等于又绕回了要查库的老路),否则很难做到”泄露后立刻失效”。所以对大多数中小规模的 AI 网关场景,不透明串 + 集中查询是更稳的选择,JWT 更适合那种下游服务本身就是无状态、每次请求量极大、宁可牺牲一点”即时吊销”能力换取零查库延迟的场景(比如网关本身要做成给几十万终端设备用的边缘网关)。
多租户 Key 的三类使用场景
| 场景 | Key 分配策略 | 典型配置 |
|---|---|---|
| 内部多团队 | 每个团队一个 key | 按团队设置月度 token 配额,用于内部成本分摊 |
| SaaS 多用户 | 每个用户/租户一个 key | 按套餐设置配额,到期或耗尽自动拒绝 |
| 开发/测试隔离 | 生产 key 和测试 key 分开 | 测试 key 限制只能用低成本模型,防止误用昂贵模型 |
| 外部 API 分发 | 每个下游客户一个 key | 独立计量用量,按量计费 |
权限粒度控制
好的网关鉴权支持以下维度的权限控制:
1. 模型白名单/黑名单
{
"key": "sk-app-a-xxx",
"allowed_models": ["gpt-4o-mini", "deepseek-chat"],
"denied_models": ["gpt-4o", "claude-3-5-sonnet"]
}
限制某个 key 只能访问特定模型,防止低权限用户调用高成本模型。
2. 功能权限
- 是否允许 function calling
- 是否允许图像生成(
/v1/images/generations) - 是否允许 embedding 接口
- 是否允许自定义 system prompt(某些场景需要固定 prompt 不让用户修改)
3. 速率限制(Rate Limiting)
per-key 限制(独立计算,互不影响):
sk-app-a-xxx: 100 RPM, 100,000 TPM
sk-app-b-xxx: 50 RPM, 50,000 TPM
sk-cust-c-xxx: 200 RPM, 500,000 TPM
网关层的速率限制在请求到达上游前即生效,既保护上游配额,也防止单个租户滥用。
4. IP 白名单
对高权限 key 或服务账号 key,可限制只允许特定 IP 段访问,防止 key 泄露后被滥用。
密钥安全管控
上游密钥安全存储:
- 上游密钥不应明文存储在数据库中,应使用 AES-256 或同等强度加密,密钥管理系统(KMS)管理加密密钥
- 日志中不打印上游密钥,响应中不透传上游密钥字段
下游网关 key 生命周期管理:
创建 key → 设置有效期(可选)→ 分发给调用方
│
├── 定期轮换(推荐每 90 天更换一次)
├── 泄露时立即吊销(网关侧撤销,无需通知上游)
└── 废弃后软删除(保留历史用量记录,供审计)
审计日志:每次鉴权验证(包括失败的)都应记录:时间戳、key ID(非明文)、请求 IP、请求模型、鉴权结果。
这里有个容易被忽略的细节:数据库里到底该存 key 的”加密结果”还是”哈希结果”。加密是可逆的,只要拿到加密密钥就能还原出明文 key;哈希(比如 SHA-256)是不可逆的,你没法从哈希值反推出原始 key,只能拿用户传上来的 key 现算一次哈希再去比对是否相等。对下游网关 key 这种”网关自己签发、只用来做比对、从不需要反查明文”的凭证,应该优先用哈希存储,即便数据库整个被拖库,攻击者拿到的也只是一堆哈希值,没法反推出能用的 key。而上游服务商的密钥因为网关自己要用它去调用上游 API(必须能拿到明文去拼请求头),才必须用加密而不是哈希——这是两类密钥在存储策略上的本质区别,很多人一上来图省事全用同一套加密逻辑处理,其实是把风险面做大了。
校验性能:每次请求都查一次库,网关会被自己拖垮
鉴权层最容易被忽视的坑不是”要不要做权限判断”,而是”判断这一步花了多久”。如果每次请求都要去关系型数据库里查 key 的状态、权限、剩余配额,QPS 一高,鉴权本身就会变成整条链路里最慢的一环——比你转发给上游模型的网络延迟还慢,这就本末倒置了。
实操上通常这么分层:
请求进来 → 先查 Redis 缓存的 key 状态(微秒级)
│ 命中 → 直接放行/拒绝
│ 未命中 → 查数据库,写回 Redis 缓存(设置 TTL,如 60 秒)
配额扣减同理,不要每次请求都对数据库做一次 UPDATE ... SET used = used + N,高并发下这类行级锁会互相排队,容易在流量高峰把数据库拖到响应变慢甚至连接池打满。更稳的做法是用 Redis 的 INCRBY 做原子自增来记实时用量,数据库只做定期(比如每分钟或每次请求异步)落盘,即使中间因为极端并发出现几个 token 的计数偏差,也远好过让鉴权层拖慢整个网关。这就是典型的”最终一致性换性能”的取舍——多租户计费场景下,配额统计允许有秒级延迟,但不能容忍鉴权本身变成瓶颈。
真实报错排查:401 和 429 是最常见的两类
“Invalid API key” / 401
这个报错在网关场景下有三种常见根因,排查顺序建议按下面来,别一上来就怀疑上游服务商:
- 先看是下游传的网关 key 本身错了(拼写、多了空格、复制的时候带了引号),这种情况网关日志里应该记录了”key 不存在”;
- 再看是不是 key 已经被禁用或过期(管理员手动吊销、或设置的有效期到了),网关日志会区分”key 不存在”和”key 已失效”这两种情况,如果你的网关日志两者不分,建议改一下,不然每次都要连表去查,排查效率很低;
- 最后才是网关自己转发到上游时,上游密钥失效或过期——这种情况下游用户看到的表现也是 401,但根因在网关这一侧的渠道配置,不是下游 key 的问题。这也是为什么审计日志里”鉴权结果”最好细分成”下游鉴权失败”和”上游转发失败”两类,否则运营同学遇到用户投诉时会两头甩锅。
“Rate limit exceeded” / 429
排查时先确认是网关层自己的限流生效了,还是网关转发给上游后被上游服务商限流。两者对下游用户的意义完全不同:网关层限流是你自己配的配额到了,应该在响应体里明确告诉调用方”当前 key 的 RPM 限制是多少、什么时候重置”;上游限流则说明这个渠道本身容量不够了,网关应该有故障转移逻辑,自动切到备用渠道而不是把 429 直接透传给下游——把上游的容量问题暴露成下游用户的错误提示,是很多自建网关最常被吐槽的体验问题。
OneAPI/NewAPI 的 key 管理实践
OneAPI 和 NewAPI 中,网关 key 对应”令牌(Token)“概念:
- 管理员持有后台账号,配置上游渠道(真实服务商 key)
- 用户/应用持有令牌(网关 key),令牌有额度和有效期
- 令牌使用时消耗额度,管理员可随时充值、暂停或删除令牌
- 管理界面可查看每个令牌的用量明细
配置要点:
- 令牌的”模型限制”字段限制可访问模型
- “额度”字段控制总消费上限(以对应 key 的计价单位计算)
- “有效期”字段自动过期,适合临时访问场景
常见问题
网关 key 泄露了怎么办? 立即在网关管理界面禁用该 key,无需更换上游密钥。这是网关统一鉴权的核心价值之一:泄露的是网关凭证,处置代价极低,不影响其他 key 和上游配置。
多个应用共用一个 key 有什么问题? 无法区分用量来源,一旦泄露影响面大,速率限制也无法精细控制。建议每个独立应用或服务使用独立 key,成本分摊和安全管控都更清晰。
网关 key 和上游 key 的命名冲突怎么处理? 在网关内部,上游密钥存储于渠道配置中,通过渠道 ID 引用,与网关 key 完全独立。对调用方而言,它只看到网关 key,上游 key 的存在对其完全透明。
为什么有的用户配额明明还没用完,却提示余额不足? 这通常是”预扣”和”实扣”没对齐导致的。稳妥的做法是流式请求还没返回完整结果之前,先按这次请求可能消耗的上限预扣一部分配额(防止同一个 key 在响应还没结束时就发起下一轮并发请求,把配额刷爆),等真实 token 用量结算出来后再把预扣和实扣的差额退回。如果网关没做预扣,只在请求彻底结束后一次性扣费,高并发场景下同一个 key 可能在短时间内发出去几十个请求,实际消耗早就超过配额了,扣费才慢悠悠地追上来——这就是所谓的”超卖”问题。是否要做预扣,取决于你的调用方并发量:内部低频调用可以不做,对外的 SaaS 或客户分发场景,并发量上来后不做预扣基本必然会出现超卖。
自测清单:怎么验证你的网关鉴权真的生效了
搭好鉴权层之后,别光看代码逻辑,花两分钟用 curl 实际测一遍,比看十遍代码都放心:
# 1. 用一个不存在的 key 请求,应该返回 401,且响应体里能看到明确的错误信息
curl -s -o /dev/null -w "%{http_code}\n" https://你的网关地址/v1/chat/completions \
-H "Authorization: Bearer sk-not-exist" -H "Content-Type: application/json" \
-d '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"hi"}]}'
# 2. 用一个被禁用模型访问受限 key,应该返回权限错误而不是转发给上游
# 3. 短时间内连续发起超过 RPM 限制的请求次数,第 N+1 次起应该开始收到 429
# 4. 用一个已吊销的 key 请求,应该立刻拒绝(验证吊销是否实时生效,还是要等缓存过期)
第 4 条尤其容易翻车:如果你的网关做了 Redis 缓存 key 状态、TTL 设了 60 秒,那吊销一个 key 之后的这 60 秒内它可能还能用——这对”内部误操作纠错”没什么问题,但对”key 疑似泄露、需要立刻拒绝”的场景就是个隐患。稳妥的做法是吊销操作走”主动失效缓存”而不是”等 TTL 自然过期”,管理后台点了禁用按钮,同时向 Redis 发一条删除缓存的指令,别指望等 TTL 自己过期。
延伸阅读:
- 多模型聚合 API 完整指南
- 用量统计与配额管理
- 统一计费与对账
- 更多聚合 API 内容见 聚合API 专题
需要企业级多租户鉴权?申请力达云聚合 API 内测,精细权限控制,开箱即用。