Chris van Eijk:Supabase RLS 策略示例,在 PostgreSQL 17 上实测

Chris van Eijk:Supabase RLS 策略示例,在 PostgreSQL 17 上实测

💡 原文英文,约2100词,阅读约需8分钟。
📝

内容提要

本文在PostgreSQL 17上实测了四套Supabase行级安全策略:仅本人可见、公开只读、团队成员可见、管理员经JWT读取。四者均能阻止用户互访数据,但团队策略若用逐行函数判断,计数耗时744毫秒,改为集合判断仅7.7毫秒。建议用security definer函数返回团队ID集合,并注意JWT角色应读app_metadata而非user_metadata。

🔎

延伸解读

团队策略的性能陷阱:逐行函数 vs 集合判断

文章实测显示,团队可见策略若用逐行函数判断成员身份,计数10万行项目耗时744毫秒;改为security definer函数返回团队ID集合后,仅需7.7毫秒。原因在于PostgreSQL不会内联security definer函数,逐行调用导致每行执行一次函数。建议将成员规则写成“行的团队ID是否在我的团队集合中”,而非“这个团队是否属于我”。

更新和删除他人数据时静默失败,客户端需检查影响行数

四套策略均能阻止用户互访数据,但更新或删除他人行时不会报错,而是返回UPDATE 0或DELETE 0。这意味着客户端若只依赖错误码判断操作成功,可能误以为操作已生效。文章提醒,若业务上需要感知此类失败,客户端应检查受影响行数。

管理员策略应读app_metadata,而非user_metadata

示例4通过JWT中的app_metadata.role判断管理员身份。文章实测发现,若策略误读user_metadata,普通用户B可自行设置role为admin并读取全部发票,而真正的管理员反而只能看到自己的数据。由于user_metadata可由已认证用户修改,依赖它做权限判断会引入安全风险。

团队策略的权限边界:成员默认可改删团队项目

在示例3中,所有团队成员均可更新和删除团队项目,策略未按角色区分。文章建议,若需限制,应在update和delete策略中利用team_members的role列。此外,不应允许用户自行插入成员关系,否则可能加入任意团队并读取其项目;应将插入限制给团队所有者或通过服务端添加。

❓

Q&A

Supabase RLS 里团队策略怎么写才能避免无限递归?

不要在 team_members 表的策略里直接子查询 team_members 自身,否则会报 infinite recursion。正确做法是在不暴露给 API 的 schema(如 private)中创建一个 security definer 函数,返回当前用户的团队 ID 集合,然后在策略里用 team_id in (select private.user_team_ids())。该函数需设置 search_path = '',并只授予 authenticated 执行权限。

为什么团队可见策略用逐行函数比用集合慢那么多?

逐行函数(如 private.is_team_member(team_id))会在过滤条件中每行调用一次,PostgreSQL 不会内联 security definer 函数,导致 100,003 行就执行 100,003 次,计数耗时 743.8 毫秒。而用集合形式 team_id in (select private.user_team_ids()) 只执行一次函数,将结果转为哈希集合逐行比对,仅需 7.7 毫秒,相差近一百倍。

Supabase RLS 中管理员角色应该从 JWT 的哪个字段读取?

应该从 app_metadata 读取,例如 ((select auth.jwt()) -> 'app_metadata' ->> 'role') = 'admin'。不要用 user_metadata,因为该字段可被已认证用户修改,会导致安全漏洞。实测中,把策略写在 user_metadata 上时,普通用户 B 伪造 role=admin 后能读取所有发票,而真正的管理员反而只能看自己的。

只写 SELECT 策略够不够?插入或更新时为什么没反应?

不够。每个操作都需要对应的策略:没有 SELECT 策略时,.insert().select() 会失败,.update().eq(…) 会静默地不修改任何行。更新策略还需要 with check 来防止把行转交给别人,否则可能用 with check (true) 造成漏洞。实测中,缺少 SELECT 策略会导致更新静默影响 0 行。

公开只读策略和仅本人可见策略能同时用在一张表上吗?

可以。同一张表上的多个 SELECT 策略会用 OR 组合。例如 posts 表上,一个策略允许 anon 和 authenticated 读取 is_published 为真的行,另一个策略允许 authenticated 读取 author_id 等于自己的草稿。实测中,anon 看到两篇已发布文章,用户 A 看到两篇已发布文章和自己的草稿,用户 B 同理。但读取公开文章不会赋予修改权,A 更新 B 的已发布文章返回 UPDATE 0。

团队策略里成员能修改团队所有项目,如何限制?

默认的团队项目策略允许每个成员更新和删除团队的所有项目。如果这权限过大,可以在 update 和 delete 策略中利用 team_members 表的 role 列来限制,例如只允许 role 为 owner 或 admin 的成员操作。另外,不要允许用户自行插入 team_members 行,否则任何人可加入任意团队并通过 user_team_ids() 读取其所有项目;应将插入限制给团队所有者或在服务端添加成员。

🏷️

标签

➡️

继续阅读