Chris van Eijk:Supabase RLS 策略失效?十种原因,实测验证

Chris van Eijk:Supabase RLS 策略失效?十种原因,实测验证

💡 原文英文,约2500词,阅读约需10分钟。
📝

内容提要

文章在PostgreSQL 17上复现了Supabase行级安全策略失效的十种原因:无策略时查询返回空、插入报错;.insert().select()需SELECT策略;upsert需INSERT、SELECT、UPDATE策略;无SELECT策略时update/delete静默无效;请求以anon身份到达或auth.uid()为空导致看不到数据;会话级设置claims会泄露上一用户数据;service_role和postgres绕过RLS;视图默认以所有者权限运行,需security_invoker=true。

🔎

延伸解读

“静默失败”比报错更危险

文章实测发现,缺少 SELECT 策略时,带 WHERE 的 update/delete 会返回 0 行且不报错,查询也直接返回空结果。这类静默失败容易被误判为“没有数据”或“逻辑正常”,而不是权限问题。排查时应先确认 RLS 是否开启、对应命令是否有策略,再用真实用户身份验证,而不是只看是否抛错。

upsert 的策略需求被低估

实测显示,即使插入的是新行,只要 upsert 指定了冲突列,就会触发 SELECT 策略检查;命中已有行时还需要 UPDATE 策略。仅配置 INSERT 策略时,upsert 会以“new row violates…”报错,容易误导排查方向。使用 upsert 时应同时准备 INSERT、SELECT、UPDATE 三类策略,并注意 generated always 身份列在发送 id 时会先报错。

用 secret key 测试等于没测

service_role 和 postgres 角色都带 BYPASSRLS,即使执行 force row level security 也仍能读取全部行。因此用 secret key 或 postgres 验证策略“正常”,并不能代表应用中以 authenticated 身份发起的请求也正常。文章建议用带真实 sub 的 claims、以 authenticated 角色测试,才能复现应用实际路径。

视图和会话设置是两个隐蔽泄露点

普通视图默认以所有者权限运行,在 Supabase 上通常是绕过 RLS 的 postgres,实测中用户 A 和 anon 都能通过视图看到全部 5 行,需设置 security_invoker = true。另外,若后端用 set_config(..., false) 按会话设置 claims,同一连接上的后续请求可能沿用上一用户的 auth.uid(),读到他人数据;应按事务设置,避免连接复用导致泄露。

❓

Q&A

为什么Supabase中RLS开启后所有查询都返回0行,插入也报错?

因为行级安全开启后,如果没有为相应命令创建策略,默认拒绝所有操作。查询会返回0行,插入会报“new row violates row-level security policy”错误。需要为每个使用的命令(如SELECT、INSERT等)添加策略。

Supabase的.insert().select()为什么在只有INSERT策略时会失败?

因为.insert().select()需要返回新行,而返回行属于读取操作,需要SELECT策略。只有INSERT策略时,PostgREST会添加RETURNING子句,但缺少SELECT策略会导致“new row violates row-level security policy”错误。添加匹配新行的SELECT策略即可解决。

Supabase的upsert操作需要哪些RLS策略?

upsert需要INSERT、SELECT和UPDATE策略。因为ON CONFLICT子句会引入SELECT策略检查,即使插入新行也需要SELECT策略;如果冲突行已存在,还需要UPDATE策略。缺少任一策略都会导致“new row violates row-level security policy”错误。

为什么Supabase中update或delete操作没有报错但也没生效?

因为update或delete的WHERE条件需要读取行,如果没有SELECT策略,WHERE找不到任何行,操作影响0行且不报错。需要添加SELECT策略,使WHERE能匹配到行,update/delete才会生效。

登录用户看不到数据,可能是什么原因?

可能有两个原因:一是请求以anon身份到达,而策略只对authenticated生效,导致查询返回0行;二是auth.uid()为null(JWT中缺少sub),使得user_id = auth.uid()永远不成立。需要确保客户端发送用户会话,并检查JWT包含有效的sub。

为什么用service_role密钥测试RLS策略会通过,但在应用中却失败?

因为service_role和postgres角色具有BYPASSRLS属性,会绕过行级安全策略。用这些角色测试时策略不生效,所以看起来通过。但在应用中请求以authenticated角色到达,策略会正常执行。测试时应使用authenticated角色并携带真实用户的claims。

通过视图查询时用户看到了别人的数据,如何解决?

因为视图默认以所有者(通常是postgres)权限运行,而postgres绕过RLS。需要创建视图时指定security_invoker = true,使视图以调用者权限运行,从而应用RLS策略。

在共享连接上设置claims会导致数据泄露吗?

会。如果使用set_config(..., false)设置会话级claims,同一连接上的后续请求可能继承前一个用户的claims,导致看到前一个用户的数据。应使用set_config(..., true)按事务设置claims,避免跨请求泄露。

🏷️

标签

➡️

继续阅读