← 返回资讯

AI 网关统一鉴权与多租户 Key 管理

2026-07-24

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

这个报错在网关场景下有三种常见根因,排查顺序建议按下面来,别一上来就怀疑上游服务商:

  1. 先看是下游传的网关 key 本身错了(拼写、多了空格、复制的时候带了引号),这种情况网关日志里应该记录了”key 不存在”;
  2. 再看是不是 key 已经被禁用或过期(管理员手动吊销、或设置的有效期到了),网关日志会区分”key 不存在”和”key 已失效”这两种情况,如果你的网关日志两者不分,建议改一下,不然每次都要连表去查,排查效率很低;
  3. 最后才是网关自己转发到上游时,上游密钥失效或过期——这种情况下游用户看到的表现也是 401,但根因在网关这一侧的渠道配置,不是下游 key 的问题。这也是为什么审计日志里”鉴权结果”最好细分成”下游鉴权失败”和”上游转发失败”两类,否则运营同学遇到用户投诉时会两头甩锅。

“Rate limit exceeded” / 429

排查时先确认是网关层自己的限流生效了,还是网关转发给上游后被上游服务商限流。两者对下游用户的意义完全不同:网关层限流是你自己配的配额到了,应该在响应体里明确告诉调用方”当前 key 的 RPM 限制是多少、什么时候重置”;上游限流则说明这个渠道本身容量不够了,网关应该有故障转移逻辑,自动切到备用渠道而不是把 429 直接透传给下游——把上游的容量问题暴露成下游用户的错误提示,是很多自建网关最常被吐槽的体验问题。

OneAPI/NewAPI 的 key 管理实践

OneAPI 和 NewAPI 中,网关 key 对应”令牌(Token)“概念:

  1. 管理员持有后台账号,配置上游渠道(真实服务商 key)
  2. 用户/应用持有令牌(网关 key),令牌有额度和有效期
  3. 令牌使用时消耗额度,管理员可随时充值、暂停或删除令牌
  4. 管理界面可查看每个令牌的用量明细

配置要点:

  • 令牌的”模型限制”字段限制可访问模型
  • “额度”字段控制总消费上限(以对应 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 内测,精细权限控制,开箱即用。