如何设计私有化部署的 Token 工厂的权限系统
私有化交付的 Token 工厂,权限问题很少从“角色怎么分”开始暴露,通常是从两个很具体的问题开始:
客户的安全团队问:这把 Key 能不能只允许调用某几个模型,每分钟最多多少次?Key 被带出去之后,影响范围有多大?
客户的平台团队问:运维能不能看到我们调了什么?能不能替我们建 Key、改配额?
这两个问题,机器学习平台的权限模型答不好。与其说它不够细,不如说它细在了另一个地方。
Go TaaS 是一个跑在 Kubernetes 上的开源大模型推理与计量计费平台:平台方部署模型、镜像和推理服务,租户用 OpenAI 兼容 API 按 token 消费。下面以它的权限设计为例,把这两条链拆开看。设计文档与代码都在 go-taas.com 和 GitHub 上公开,文末附了本文引用到的章节链接。
私有化交付的 Token 工厂,权限问题很少从“角色怎么分”开始暴露,通常是从两个很具体的问题开始:
客户的安全团队问:这把 Key 能不能只允许调用某几个模型,每分钟最多多少次?Key 被带出去之后,影响范围有多大?
客户的平台团队问:运维能不能看到我们调了什么?能不能替我们建 Key、改配额?
这两个问题,机器学习平台的权限模型答不好。与其说它不够细,不如说它细在了另一个地方。
Go TaaS 是一个跑在 Kubernetes 上的开源大模型推理与计量计费平台:平台方部署模型、镜像和推理服务,租户用 OpenAI 兼容 API 按 token 消费。下面以它的权限设计为例,把这两条链拆开看。设计文档与代码都在 go-taas.com 和 GitHub 上公开,文末附了本文引用到的章节链接。
一、先看 Token 工厂在卖什么
Token 工厂有两条价值链。
生产段是:集群 → 镜像 → 模型 → 推理服务 → 路由 → 定价。这一段追求稳定、可控、可审计,动作频率低,但每一次误操作的成本都很高。
消费段是:组织 → 项目 → 成员 → API Key → 限额 → 账户余额。这一段追求自助、可计量、可追责,动作频率高,但单次操作的影响可以被限额收住。
两条链的权限诉求几乎相反。生产段需要“少而强”的权限:能动机器的人越少越好,每个动作都要留痕。消费段需要“多而弱”的权限:每个开发者都能自己建 Key、看用量、排查失败请求,但不能越过组织边界,也不能把额度打爆。
一个简单的判断方法,是看动作失败之后谁承担成本。
- 推理服务部署错了,成本在平台:GPU 占着、显存浪费、集群可能被拖垮。
- 多调了 100 万 token,成本在租户:月底账单会体现。
- Key 泄露了,风险在租户,但止损动作在平台:限流、吊销、封禁。
权限边界应该沿着成本责任划,而不是沿着资源目录划。这是 Token 工厂和机器学习平台最根本的分歧。
二、机器学习平台的四层 RBAC,为什么搬不过来
机器学习平台的典型结构是“平台 → 组织 → 项目 → 用户”四层,每一层都要对模型、镜像、推理服务做管控。原因很实际:每一层都可能是资源的创建者。
平台管理员维护集群和基础镜像;组织管理员决定组织能用哪些模型;项目管理员在项目里创建训练任务和推理服务;普通用户在项目里使用这些资源。角色矩阵自然是“层级 × 资源 × 动作”,再加上项目级的命名空间隔离和配额。
Token 工厂不是这个形状。
| 维度 | 机器学习平台 | Token 工厂 |
|---|---|---|
| 主要用户 | 算法与平台工程师 | 应用开发者、智能体、业务方 |
| 核心资源 | 训练任务、模型、镜像、推理服务 | API Key、配额、余额、调用量 |
| 隔离单位 | 项目、命名空间 | 组织(账单)、项目(预算)、Key(调用) |
| 权限主体 | 人 | 数据面是 Key,控制面才是人 |
| 配额含义 | 能用多少算力 | 能花多少钱、能打多快 |
| 典型风险 | 越权看到别人的模型与服务 | Key 泄露、超额消费、跨租户调用 |
落到层级上,差异更直观:
| 层级 | 机器学习平台 | Token 工厂 |
|---|---|---|
| 平台 | 集群、镜像、模型、服务编排、全平台配额 | 生产端资源、组织目录、定价与账户 |
| 组织 | 组织内模型授权、成员、配额 | 成员、邀请、项目、配额、用量与账单 |
| 项目 | 项目内的训练与推理资源、成员、配额 | 项目成员与预算切分,不触碰生产资源 |
| 用户 | 在项目内创建和使用资源 | 建 Key、设限速与模型范围、看用量、余额与账单 |
看这两列:机器学习平台的模型、镜像、推理服务在四个层级上都要有对应能力;Token 工厂的生产资源只出现在平台一行,下面三行全是成员、预算、凭证和账单。权限系统的复杂度,就是从这两列长出来的。
生产端的模型、镜像、推理服务由平台方统一供给,租户不关心副本数、镜像 digest、权重路径,也不关心服务落在哪个节点。租户关心的是另外四件事:我能用哪些模型、每分钟能打多少请求、这个月还能花多少钱、这把 Key 泄露了怎么办。
所以权限设计的第一刀,先切开生产端和消费端,再谈角色。
如果产品要给租户开放“在配额内部署专属推理服务”,那也只是把一部分生产配额租给他们,不是交出生产端的管理权。模型目录、镜像仓库、集群和定价仍然只属于平台。这个区别要在权限模型里写死,否则“租户能部署”很容易被实现成“租户能改模型”。
三、四个面,而不是一棵权限树
把权限切开之后,系统里其实有四个面,而不是一棵从上到下的树。
| 面 | 入口 | 主体 | 典型操作 |
|---|---|---|---|
| 平台管理面 | /admin 、/api/v1/admin/* | platform-admin | 组织、模型、镜像、服务、定价、账户 |
| 租户治理面 | 同一管理端,组织/项目域 | owner、admin、项目管理员 | 成员、邀请、项目、配额 |
| 用户自助面 | / 、/api/v1/* | 组织成员 | Key、用量、日志、账单、Playground |
| 数据面 | /v1/chat/completions 等 | API Key | 推理调用 |
这里的“面”不是 UI 分组,是鉴权边界。四个面,四条信任链,谁也不复用自己的凭证。
/api/v1/admin/ 前缀属于管理面,其余 /api/v1/ 属于用户面。两个面各有自己的会话。会话的 realm 由服务端签发:密码登录直接按角色集合裁决,命中管理角色集合的得到 admin,否则得到 user;SSO 登录则绑定在入口上,从 /admin/login 进来的回调签发 admin 会话。无论哪条路径,realm 都不是请求参数,URL 和请求体都改不了它。
结果是两个很实际的约束:
第一,一个人即使既是平台运维、又是某个租户的成员,也不需要“一个会话两种身份”。他在两个面各有一个会话,浏览器里存放在两个不同的键里。要从用户面切到管理面,走一次 SwitchSurface,为目标 realm 重新铸造一个会话;源会话保留,可以切回来。切换不复用源令牌,相当于换一张票。
用户端左下角就是这个人的身份卡片:账号、PLATFORM-ADMIN 徽标,以及“切换到管理端”的入口。管理端反过来提供“切换到用户端”。两边各自持有自己的会话。
第二,错面的令牌不能越面使用。用户面会话打到 /api/v1/admin/*,在进任何 handler 之前就被拒绝,返回 10038 REALM_MISMATCH。这条规则把“前端忘了隐藏菜单”这类问题挡在了后端之外。过渡期的无会话访问是另一个显式开关,第九节单独说。
管理端内部还有一层收敛。进入管理端时,服务端先算出“你能进入哪些管理域”:会话带 platform-admin 的看到平台域;成员行是 owner/admin 的看到对应组织域;项目成员行是 admin 的看到对应项目域。前端按这个列表渲染入口,后端仍然逐调用校验。UI 只是提示,不是边界。
同一个人如果在平台、组织、项目三处都有管理身份,登录管理端时看到的是三个入口,而不是一棵把三种权限混在一起的导航树。选哪个域,就进入哪一套页面与 API。
四、角色:四档固定角色,外加一个不在成员表里的角色
组织内的角色是封闭集合,只有四个:owner、admin、member、viewer。项目内是三个:admin、member、viewer,没有 owner,项目归其所属组织所有。
| 角色 | 能做什么 | 边界 |
|---|---|---|
| owner | 组织的一切操作 | 设计上每组织唯一,不可移除、不可降级 |
| admin | 成员、邀请、项目、组织级配置 | 不能动 owner |
| member | 使用组织资源、创建 Key(归属组织) | 不能管成员与组织配置 |
| viewer | 只读 | 不能变更组织数据 |
角色比较用“至少”语义:viewer < member < admin < owner。需要 admin 的接口,owner 天然满足;需要 member 的接口,admin 和 owner 也满足。固定角色加固定次序,好处是可测试:每个接口只需要写一个最低角色,不需要维护一张谁继承谁的图。
成员页只做两件事:把角色写清楚,把受保护的角色标出来。“所有者(受保护)”这句话不是提示文案,是 10032 的前端投影。
owner 是受保护的。移除 owner、把 owner 降级,都会返回 10032 OWNER_PROTECTED,包括 owner 对自己操作。唯一路径是显式转移。这不是为了防攻击,而是为了防止一个组织在几次人事变动之后没有 owner——没有 owner 的组织不可治理,最后只能由平台进数据库修。
成员与邀请的完整规则见组织成员、角色与邀请。
第四个角色是 platform-admin,但它不在成员 API 的可分配范围里。它挂在账号上,通过一个专用的平台组织成员行承载。解析角色时,如果调用方在目标组织里没有成员行,而会话又带着 platform-admin,就解析为平台管理员;在组织级操作上,它与 owner 同级。这样平台运维不需要在每个租户里都插一条成员记录,也不会因为某个租户漏加了成员而失去运维能力。反过来说,如果平台管理员同时是某个组织的真实成员,成员行优先——他在这个组织里的身份仍然受成员表约束。
这里有一个刻意的不对称:平台管理员可以做组织级操作,但不能替租户管项目。项目管理走的是“只认成员行”的校验,平台管理员的会话角色不满足它。
1 | // 组织级操作:平台管理员与 owner 同级 |
这是职责分离,不是实现遗漏。生产端运维应该能建组织、配模型、看集群,但不应该顺手改租户的项目结构和人员预算。私有化交付里,这条边界往往比“多一个管理员”更能让客户的安全团队接受。
五、租户边界:成员表是权威,IdP 只是提示
私有化部署的客户通常有自己的身份源:LDAP、OIDC、SAML,或者企业微信。SSO 登录时,IdP 会带回来组和角色声明。一个很自然的实现是把这些声明直接映射成平台角色,然后放行。
这个做法在演示环境里没问题,在生产环境里是隐患。IdP 的声明是“身份提供方认为这个人是谁”,不是“这个人在本平台属于哪个租户”。声明可能过期、可能被错误配置、可能因为组名冲突把一个人放进错误的组织。权限系统不能把“声明”当“事实”。
平台的做法是:org_members 是唯一权威(多租户隔离定了组织与项目的行,SSO 联邦只负责认证)。会话里的可访问组织和每个组织的角色,都从成员表派生。IdP 声明最多用来播种待处理的成员关系,绝不能用它授予成员表没有授予的访问。
自助进入租户有两条路径,都落到同一张表。 管理员可以通过邀请拉人:邀请令牌只存哈希,原始令牌只在创建时返回一次,默认 7 天过期,接受时必须匹配被邀请的邮箱,接受后成员关系才生效。用户也可以主动申请加入组织或项目,由管理员审批。企业内网里,申请往往比邮件邀请更现实——不是每个人都有可收外部邮件的邮箱。
组织存在性与操作权限是两件事。OrgGuard 回答“这个组织是否存在”,不存在返回 10005;RoleGuard 回答“这个人能不能在这个组织里做这件事”,不满足返回 10036。两个检查分开,错误语义才清楚:组织不存在是调用方传错了,权限不足是调用方越界了。
用户面会话不认组织头。 有用户面会话时,X-Organization-Id 被忽略(见控制台面分离),组织只从会话的活动组织取。否则一个租户只要改个请求头,就能把页面指向另一个组织。过渡期允许无会话访问时,这个头仍然作为载体存在,但必须先对照真实组织校验,未知 id 返回 10005,而不是静默创建。
私有化场景下,账号最多归属一个组织、一个项目。 这是这一版的选择,不是通用规律。SaaS 里一个人属于多个组织很常见,私有化交付里,“人”通常属于一个客户,多租户是平台侧的概念,不是个人侧的概念。跨租户身份只留给平台管理员。这样成员关系更接近“归属”而不是“订阅”,会话里的组织解析、账单归属、项目归属都简单很多;代价是同一个人不能同时服务两个租户,需要另开账号。
六、消费端:Key 是授权书,限额是条款
生产端的权限靠角色,消费端的权限靠凭证。这是两套不同的东西,不能混。消费端的权限最终落在四类对象上:Key、配额、限速、余额。
Key 本身要能自证边界
API Key 的格式是 sk- 加 43 位 base62 字符,明文只在创建时展示一次。数据库里存两个东西:明文的 SHA-256 摘要用于查找,Argon2id 加盐哈希用于常量时间比对。列表页只显示前 8 位前缀。
Key 归属于组织,不归属于个人。吊销是软状态,行保留,因为计量、结算和审计都要靠它。过期等同于吊销。数据面解析出来的角色固定是 agent,不是某个人的组织角色。细节见 API Key 生命周期管理。
用户端的 Key 列表恰好把权限相关的四项摊开:前缀(可辨认、不可复原)、速率限制、模型范围、状态。这四列就是数据面能执行的边界,没有第五列。
这一点很关键:数据面的权限主体不是人,是 Key。 人只在控制面出现。Key 的权限不是人的权限的拷贝,而是组织授权的一个子集。人员离职、换岗、换团队,不需要迁移权限,收回或吊销相关 Key 就行;一把 Key 泄露,影响范围由这把 Key 自己的范围决定,不会自动扩散成这个人的全部权限。
有效权限 = 组织授权 ∩ Key 范围
模型权限有两层,分别属于两个主体。
第一层是组织级授权,属于平台(按租户模型授权)。模型默认对所有组织开放:授权表里零行,表示目录公开;一旦这个模型有了任何一条授权行,它就翻转为受限,只有被列出的组织能用。这个“默认放行”是为了兼容已有目录,不是为了省事。第一次授权就是显式收紧的动作。授权在部署和调用两处都检查:只卡部署,已经在线上的服务会继续为被撤销的租户服务;只卡调用,部署方会建出一个自己根本用不了的服务。
第二层是 Key 级范围,属于租户(API Key 模型范围限定)。Key 可以带一个模型白名单,空列表表示“组织已授权的全部模型”。这个白名单由租户在用户控制台自己维护,平台运维只读,不代管。
真正生效的权限是两者的交集:Key 只能调用“组织被授权、且 Key 白名单里也有”的模型。两个失败要能区分:Key 范围不允许返回 10039,组织没有授权返回 10105。对调用方来说都是 403,但排障方向不一样:一个指向“你的 Key 配窄了”,一个指向“你的组织没有这个模型”。
这也是生产端和消费端权限最干净的分工:平台划外边界,租户在里面再收窄。平台不需要知道某个团队把 Key 配成了什么样,租户也不可能通过改 Key 绕过组织的授权。
举个具体的例子。组织被授权了 Qwen 和 DeepSeek,其中 DeepSeek 同时还授权给了另一个组织,所以它处于受限状态。A 团队的 Key 白名单只写了 Qwen,调 DeepSeek 会被 10039 拒;B 团队的 Key 没设白名单,就能调 DeepSeek。后来平台撤销了本组织的 DeepSeek 授权,A、B 两把 Key 再调都会返回 10105。平台只动了一次授权,所有 Key 的上界同时收紧;各团队内部的收窄互不影响。
这里也能看出“零授权=公开”的代价:如果撤销的是模型的最后一条授权,模型会回到对所有组织开放,而不是对所有人关闭。要真正下线一个模型,不能靠清空授权表,得走模型删除,或者补一个显式的拒绝语义;这个边界必须在运维手册里写清楚,否则一次“清理授权”会把私有模型变成公开模型。
配额是一棵树,但要和凭证粒度对齐
配额在平台里不是一个数字,是一棵树。
| 层级 | 谁设定 | 管什么 | 作用位置 |
|---|---|---|---|
| 组织 | 平台管理员 | 并发、服务数、每日批量、单请求输出 token | 创建路径与数据面 |
| 项目 | 组织管理员、项目管理员 | 同一组维度的项目预算 | 控制面预算分配 |
| 成员 | 项目管理员、组织管理员 | 个人的项目预算 | 控制面预算分配 |
| Key | 组织成员 | RPM、TPM、模型范围 | 数据面网关 |
| 账户 | 平台管理员 | 余额、月度额度、消费上限 | 计费链路 |
组织配额是平台设的天花板(服务配额管理),四个维度分别是 max_concurrent_requests、max_inference_services、max_batch_jobs_per_day、max_output_tokens_per_request。全部是整数,0 表示不限;一旦设了值,必须落在文档写明的范围内,比如并发是 1 到 10000,单请求输出 token 是 64 到 32768。项目配额和成员配额是租户内部的二次分配:组织管理员把预算分给项目,项目管理员再分给人。它们回答的是“内部怎么分”,不是“平台允许多少”——后者始终由组织配额决定。这样一个人拿到的额度不会因为项目整体很宽裕而被悄悄放大。
项目管理员的视角是“给每个人分多少”,而不是“这个项目总共多少”。所以成员配额是逐人的四个维度,改一次只影响一个人。
这里有一个容易做错的地方:数据面的执行粒度,必须和凭证的粒度对齐。 Key 是组织级的,所以热路径上能直接执行的是组织级配额和 Key 级限流。项目和成员的配额是控制面的预算,不能指望网关在每个请求上再解析一次“这个 Key 属于哪个项目、这个人是谁”。那会把一次鉴权变成三次查库,延迟和故障面都不可接受。如果确实需要项目级的热路径隔离,正确做法是让 Key 自身携带项目归属,而不是在请求路径上做归属推导。
这也是为什么 Key 挂在组织而不是项目下:Key 一旦挂在项目下,数据面就要先解析 Key → 项目 → 组织,才能算限额和账单;中间任何一层变更都会影响在线调用。组织级 Key 让归属解析只有一跳,项目则留在控制面做人和预算的切分。
余额、额度、消费上限是三件事
很多系统把这三个概念揉成一个“余额”,最后账单很难对清。
预付费账户看 balance_cents,充值进来,结算时扣减,余额小于等于 0 就阻断(余额与配额账户模式)。后付费账户看 monthly_quota_cents 和 used_this_cycle_cents,周期内累计,超额之后由 overdraw_policy 决定是阻断还是只告警。这两个是账户模式自带的记账字段。
monthly_spend_limit_cents 是第三个东西:跨模式的月度消费上限,和账户模式无关(按 Key 限流与组织消费上限)。它是一道安全网,防的是“预付费余额还有很多,但这个月已经花超了”或者“后付费额度很宽,但业务方不想再超预算”。把消费上限做成后付费配额的一个别名,是两个概念混成一个字段的典型错误。
金额一律用整数分,不用浮点。结算时扣减,不做按请求冻结。代价是小时级结算意味着组织可能在最后一个小时超额,这是明确接受的风险;收益是数据面不需要分布式锁,也不会因为冻结失败把正常请求卡死。账本只追加,充值、扣减、退款各一条,幂等键唯一,重放不会重复入账。
余额的充值和退款是平台动作,租户能看到余额、账单和消费上限;自助支付是后续能力。把“看得到”和“改得了”分开,比给租户一个充值按钮更重要。
七、数据面:一次校验,把判定带下去
消费端的策略最终要在数据面执行,但数据面不能变成第二个权限系统。架构与网关插件的细节见架构设计与数据面网关插件。
平台的边界很清楚:Wasm 插件不做业务判断。它解析 Authorization 头,把 Key 摘要和模型名通过 gRPC 发给 auth,由 auth 完成哈希比对、吊销与过期检查、Key 范围检查、组织模型授权检查。插件只负责调用、缓存和执行判定结果。
VerifyAPIKey 的返回不是一个布尔值,而是一份判定结果:组织 id、Key id、角色、RPM、TPM、组织并发上限、单请求输出上限。模型范围和组织授权在 auth 内部就已经判完,不需要把白名单发到网关上再判一次。网关拿到这份结果,就能在本地完成 RPM 限流和 max_tokens 截断,不需要为每个请求再加一次 RPC。TPM 要按响应 token 计量,无法只看请求头就判掉,因此它随判定下发,由具备 token 视角的执行点来用。
缓存有两层。插件内有一层带 TTL 的本地缓存,按 Key 摘要和模型做键;auth 后面还有一层 Redis 正缓存。本地缓存是 per-worker 的,N 个 worker 最多会让平台在每个 TTL 内看到 N 次校验,这是公开的取舍;Redis 那层把这些回源变得很便宜。
缓存带来一个必须写进文档的属性:吊销的生效上界等于缓存 TTL。 控制面吊销是立即生效的,但已经缓存的判定还会活到 TTL 过期。默认 30 秒,意味着最坏情况下泄露的 Key 还能再用 30 秒。这个数字要和安全团队对齐,不能含糊。Key 的范围变更会主动删除 Redis 里的正缓存,和吊销走同一条传播路径;网关本地缓存仍然只能等 TTL 过期。
执行顺序是:鉴权 → 限流 → 配额 → 资金。这是设计上的顺序契约,具体执行点可以分别落在网关和计费链路,但顺序不能反。
顺序不是随意的。未认证的请求应该比被限流的请求更便宜地被拒掉,所以鉴权在最前面。被限流的 Key 根本不应该走到资金检查,否则一次限流会变成一次账户查询。资金检查放在最后,因为它最贵,也最不应该影响前两步的判断。
错误码要分开,因为 SDK 的退避逻辑靠它:
| 场景 | HTTP | 业务码 |
|---|---|---|
| Key 不存在、非法、吊销、过期 | 401 | 10007 / 10008 / 10009 / 10010 |
| Key 范围或组织授权不允许该模型 | 403 | 10039 / 10105 |
| 超过 Key 的 RPM | 429 | 10037 |
| 超过单请求输出上限 | 400 | 13304 |
| 余额或额度不足 | 402 | 10502 |
| 平台自身无法完成校验 | 503 | — |
把 403 和 429 混成一个“请求失败”,调用方就会用错退避策略:该换 Key 的时候在重试,该退避的时候在换 Key。把 402 做成 429,财务问题就会被当成限流问题排查。错误码是权限契约的一部分,不是附属品。
八、失败路径:身份 fail-closed,计量 fail-open
权限系统好不好,往往在依赖挂掉的时候才看得出来。
| 故障 | 策略 | 原因 |
|---|---|---|
auth 或 Redis 不可用 | 拒绝,返回 503 | 未经验证的流量不能进推理实例 |
| Key 缓存读失败 | 拒绝 | 读不到判定,就不能假设它通过 |
| 数据面读取组织配额失败 | 降级为不限,记录告警 | 配额读故障不应该停掉全部推理 |
| 模型授权缓存失败 | 回源数据库 | 缓存不是权威,数据库才是 |
| 计量上报失败 | 放行,事后按 request_id 对账 | 计量的故障半径应该是账单,不是业务 |
| 结算延迟 | 允许周期末超额 | 不做按请求冻结的已知代价 |
这些策略看起来不一致,其实按同一条标准分类:这个判断错了之后,谁疼。
身份判断错了,未授权流量进了推理实例,平台和租户都疼,而且不可逆,所以必须 fail-closed。计量错了,账单不准,可以事后按 request_id 对账补偿,业务不能停,所以 fail-open。数据面配额读错了,用户看到“不限”,热路径的并发上限暂时失效,但创建路径的服务数配额仍然生效,损失有边界,所以可以降级。资金判断错了,会直接产生坏账,所以资金侧仍然要拒绝。
权限系统不可能只有一个失败策略。把每一类故障的取舍写清楚,比统一喊一句“高可用”有用得多。
九、私有化部署带来的额外约束
同样是 Token 工厂,私有化部署和公有云 SaaS 的权限设计还有几处不同。
身份是客户的,授权判定必须留在平台内。 IdP 可以是客户的 LDAP、Keycloak、AD,但“这个人能不能在这个组织里建 Key”不能反问 IdP。私有化环境经常限制外网,权限链路里任何一次外部调用都是可用性风险。平台的判定全部在本地完成,IdP 只负责认证。
管理角色集合要可配置(统一登录与基于角色的路由)。 有的客户在 IdP 里把组织管理员叫 org-admin,有的叫 tenant-admin,有的直接用 admin。平台把这些声明映射成会话角色之后,哪些角色算“管理角色”、登录后进哪个面,是部署策略,不是代码常量。默认集合可以是 platform-admin、org-admin、admin、owner,但必须允许改。
不删除,只禁用。 组织有 active 和 disabled 两种状态,禁用阻断新建资源,但保留读取和历史。生产 Key 用吊销而不是删除,行保留;删除只用来清理测试 Key。邀请过期后只能重发,旧令牌不能复用。私有化交付里,客户的安全审计往往要求追溯两年前的某次调用是谁授权的;硬删除会把这部分证据一起删掉。
变更要可归因。 成员变更、角色变更、邀请创建与撤销、配额变更、Key 范围变更、Key 吊销,都要写审计事件:操作者、动作、资源、结果、时间。配额变更还要记录每个维度的旧值到新值。客户的安全审计不是看“有没有日志”,而是看能不能回答“是谁在什么时候把这条限额从 50 改成了 100”。
投影也是权限。 租户能看到的模型列表,不包含权重路径、版本列表和授权行;外部模型只给提供方名称,不给 provider_id、上游模型 id、base URL 和上游密钥。这不是前端裁剪,是服务端返回的投影本来就不带这些字段。权限不只是“能不能调用”,还包括“能看到对象的哪一面”。把编排内部信息暴露给租户,等于把攻击面也一起交出去。
平台不代管租户的 Key。 管理面不提供创建、改范围或吊销 Key 的入口,最多只读地看到 Key 的数量和范围,拿不到明文。Key 是租户的凭证,平台的角色是保证它被正确验证,不是替租户保管。配额同理:平台设组织的天花板,不替租户分配项目与成员预算。
运维看的是账,不是内容。 请求日志和 Playground 留在用户面,管理面只有聚合用量、账单和凭证级成本;网关日志对提示词做脱敏。私有化客户问“运维能不能看到我们调了什么”时,答案应该是:能看到用量、成本、错误率,看不到提示词和响应内容。
过渡模式要能关。 早期为了兼容 CLI 和端到端测试,系统允许无会话、只带 X-Organization-Id 的访问。这个模式在私有化生产环境里应该关闭。它不是一个权限模型,只是一个迁移载体;留着它就是留了一个“知道组织 id 就能进去”的后门。
十、可以带走的六条
- 权限跟着成本责任走,不跟着资源目录走。 生产端的误操作成本在平台,所以生产端的操作收窄到 platform-admin;消费端的超额成本在租户,所以把 Key 和限额的自助权交给租户,把余额做成租户可见、平台可调的账户。
- 控制面做角色,数据面做凭证。 角色是给人的,凭证是给智能体的。把组织角色检查塞进每个推理请求,只会增加延迟,不会增加安全。
- 授权在外层收口,在内层收窄。 平台用组织授权划外边界,租户用 Key 范围在里面再收一层。两层都检查,两个错误码分开。
- 判定结果携带限额,不要让网关逐请求回调。 一次校验把组织、Key、限流和配额带回来,模型范围在权威侧判完;执行点才能留在流量所在的位置。
- 默认值要有业务语义。
0表示不限,空范围表示“组织已授权的全部”,零授权行表示目录公开。每个默认值都要能回答“为什么不是另一个值”,否则运维迟早会误读。 - 错误码是权限契约。 401、403、429、402 各有各的处理人:换 Key、换模型、退避、充值。混在一起,排障成本会转移到最贵的那一环——客户支持。
权限系统做得好不好,不看角色有多少,看两件事:租户能不能自己把事做完,平台能不能在出事的时候把影响收住。
前者靠自助和投影,后者靠凭证边界和失败策略。两者都做到了,才叫私有化部署的 Token 工厂权限系统。
参考资料
文中提到的设计文档都可以在官网文档区按主题查阅,也可以直接打开下列章节:
- 项目主页:https://go-taas.com/
- 文档中心:https://go-taas.com/docs.html
- 组织成员、角色与邀请(RBAC):https://go-taas.com/docs.html?doc=design/org-members-rbac&lang=zh-CN
- 组织/项目多租户隔离:https://go-taas.com/docs.html?doc=design/multi-tenancy&lang=zh-CN
- 控制台面分离(用户端 / 管理端):https://go-taas.com/docs.html?doc=design/console-surface-separation&lang=zh-CN
- 统一登录与基于角色的路由:https://go-taas.com/docs.html?doc=design/unified-login-role-routing&lang=zh-CN
- SSO 联邦登录:https://go-taas.com/docs.html?doc=design/sso-federation&lang=zh-CN
- API Key 生命周期管理:https://go-taas.com/docs.html?doc=design/api-key-management&lang=zh-CN
- API Key 模型范围限定:https://go-taas.com/docs.html?doc=design/api-key-model-scoping&lang=zh-CN
- 按租户模型授权:https://go-taas.com/docs.html?doc=design/model-authorization&lang=zh-CN
- 服务配额管理:https://go-taas.com/docs.html?doc=design/service-quota-management&lang=zh-CN
- 按 Key 限流与组织消费上限:https://go-taas.com/docs.html?doc=design/rate-limits-spend-limits&lang=zh-CN
- 余额(预付费)与配额(后付费)账户模式:https://go-taas.com/docs.html?doc=design/balance-quota&lang=zh-CN
- 架构设计:https://go-taas.com/docs.html?doc=design/architecture&lang=zh-CN
- 数据面网关插件:https://go-taas.com/docs.html?doc=architecture/data-plane-gateway-plugin&lang=zh-CN
- 源码仓库:https://github.com/go-taas/go-taas
















