访问控制
最后更新:2026 年 8 月 26 日
访问控制决定的是谁有资格碰每一张 Table。它回答的问题和认证不同:登录确立的是_调用方是谁_,访问控制确立的是_这个调用方可以做什么_。这套模型有意做得简单 —— 按 Table 划分 Read 和 Write,按 Function 划分 Execute,面向三类调用方 —— 再配合按 Player 划分的行归属。
默认情况下,游戏 client 能读不能写,而一张新建的 Table 对 client 是完全关闭的。你要按调用方、按 Table 有意识地逐一放开。如果 client 的调用因权限不足被拒,说了算的是运行时 —— 客户端一侧的检查 可以提前拒绝,但它从不授予权限。
三种 subject
每一次运行时调用都会解析出唯一一个 subject,依据是它本就携带的凭据。
| Subject | 是谁 |
|---|---|
| client | 你的游戏客户端 —— 公开的 pk_... key,无论有没有已登录的 Player |
| server | 你自己的服务器 —— 保密的 sk_... key(game server、脚本、CI) |
| backend | 你的服务端 Functions —— 云函数那一层 |
Backoffice(操作者)那一层是独立的,从不受这些 Table 设置的限制。
两层叠加
访问权限按固定顺序判定:先看权限,再看行作用域。
- Table 权限 —— 这个 subject 究竟能不能读或写这张 Table?不能,则调用以
403被拒,后面的一概不看。 - 行作用域 —— 对于处在 owned by a player 的 Table 上的 client,哪些行可见?Player 看得到自己的行;别人的行不可见。
Table 权限
每张 Table 都为每个 subject 各带一个 Read 开关和一个 Write 开关。Write 涵盖所有修改 —— 创建、更新和删除一视同仁,没有单独的删除权限。
| Subject | Read 默认 | Write 默认 |
|---|---|---|
| client | off | off |
| server | on | on |
| backend | on | on |
新建的 Table 在两个方向上都是对 client 关闭的,而对 server 和 backend 开放。这就是为什么 client 对一张新 Table 调用 Update() 会被拒,直到你放开 client.write —— 这道写入边界是安全默认值,不是 bug。
在一张并非 owned by a player 的 Table 上放开 client.write,等于让任何 client 都能写它的任何一行 —— 也就是共享状态、世界状态那种场景。只有 owned by a player 的 Table 才会把 client 的写入限制在调用方自己身上(见下)。放开无限制的 client 写入,请出于明确的判断。
client 的行归属
在标记为 owned by player 的 Table 上,Table 权限依然决定 client 能不能 碰它;行归属接着决定 能碰哪些行。
- Read
owner—— client 只看得到自己的行。 - Read
public—— client 看得到所有行(归属不限制读取)。 - Write —— client 创建时,会把当前行动的那个 Player 盖章为归属者;更新和删除只在调用方自己的行上才成功。
- server 和 backend 从不受限 —— 它们读写任意行。
归属于其他 Player 的行被当作不可见,而不是“禁止访问”:读取、更新或删除它都返回 404 —— 与一行根本不存在时完全相同的结果。这是有意为之,好让写入路径永远不会泄露读取路径藏起来的行。
既然他人的行和不存在的行在设计上就无从区分,就不要在 client 代码里依赖这个差别 —— 两者都只会返回一个普通的 not-found。
Function 的 Execute
每个 Function 都为各个 subject —— client、server、backend —— 各带一个 Execute 开关,默认全部开启。没有 Execute 的 subject 会以 403 被拒。
Execute 与 Function 的 Auth 要求(none 或 player)是两回事:Auth 管的是_匿名 client 能不能调用_,Execute 管的是_这个 subject 是否被允许_。没有已登录 Player 的 client 去调用一个 player auth 的 Function,无论 Execute 如何都会以 401 被拒。
调用被拒时
| 情形 | 结果 |
|---|---|
| subject 在该 Table 上没有 Read | 403 table_read_forbidden |
| subject 在该 Table 上没有 Write | 403 table_write_forbidden |
| subject 在该 Function 上没有 Execute | 403 function_execute_forbidden |
| 匿名 client 读取按 owner 划分作用域的 Table | 403 owner_read_requires_player |
| 匿名 client 写入 owned 的 Table | 422 acting_player_required |
client 在没有 Player 的情况下调用 player auth 的 Function | 401 player_auth_required |
| client 碰了归属于其他 Player 的行 | 404 not_found(不可见) |
| client 读取其他 Player 的账号 | 403 player_read_forbidden |
如果 client 的写入意外被拒,先看这张 Table 的 client.write 开关,再看它是不是 owned by a player、以及你是否正以那一行归属者的身份行动。这两点合起来,几乎能解释数据面上的每一个 403 / 404。
下一步
- Data Mutation & Model Extending —— 受这些规则把关的
Update()/Delete()调用 - Schema Object Reference —— Table 上的 owned-by-player 设置与读取策略
- Functions —— 在哪里配置每个 Function 的 Execute 和 Auth 要求