
PostgreSQL启用RLS后查询全空?默认拒绝可能正在生效

PostgreSQL 表启用行级安全(RLS)后,已有数据却在 SELECT 中得到空结果,可能是当前请求没有适用的读取策略。PostgreSQL 的行安全规则规定:启用 RLS 后,普通访问必须得到策略允许;没有策略时默认拒绝,表中的行对这类请求不可见。
空结果也可能来自查询本身,不能只凭表里有数据就认定策略出了问题。Supabase 的空数组排查说明将无策略、请求不满足策略,以及过滤条件没有匹配行列为这一症状的原因。排查顺序因此是:先确认请求查的是哪些行,再确认这些行对该请求身份是否可见。
先区分查询过滤与访问限制
从发生问题的同一个客户端发起一次简化的 SELECT,暂时去掉等值过滤、搜索条件、排序后的分页偏移量等附加条件,但保持项目、表和登录状态不变。如果简化查询能返回记录,就逐项恢复原条件,找出使结果变空的部分。改变客户端或认证方式后再比较结果,会把查询条件与身份差异混在一起。
还要确认应用实际访问的数据库、schema 和表。管理界面里某张表有记录,只能证明那张表有记录;应用若连接到另一环境,或查询另一 schema 下的同名表,修改原表的策略不会改变结果。记录响应时同时保留错误信息:行被 RLS 过滤通常表现为没有匹配行,缺少表级 SELECT 授权则可能直接返回权限错误。这两种情况需要查的配置不同。
确认 RLS 开关与 SELECT 策略
以 public.orders 为条件示例,先在有权限的数据库连接中执行 SELECT relrowsecurity, relforcerowsecurity FROM pg_class WHERE oid = 'public.orders'::regclass;。relrowsecurity 表示目标表是否启用 RLS,relforcerowsecurity 表示表所有者是否也受行策略约束。若前者为 false,这张表的 RLS 就不是当前空结果的解释,应回到连接目标、查询条件和表级权限继续核对。
接着查看目标表的策略,例如查询 SELECT policyname, roles, cmd, permissive, qual FROM pg_policies WHERE schemaname = 'public' AND tablename = 'orders';。这里要同时核对策略适用的角色、操作类型和 USING 条件。一次 SELECT 需要适用于该请求角色的 SELECT 或 ALL 策略;只有 INSERT、UPDATE 或 DELETE 策略,不能据此推断读取已经获准。策略即使写在正确的表上,若角色不匹配,对这次请求也不起作用。
USING 条件决定哪些既有行可见。条件对某行得出 true,该行才可能进入 SELECT 结果;得出 false 或 NULL,该行会被排除。多条宽松策略以“或”组合,限制性策略再以“且”收紧范围;仅有一条看似放行的策略,不保证最终能看到记录。检查时应把目标行的字段值与表达式逐项对应,尤其留意条件中使用的用户标识是否与实际请求相同。
把请求角色和 JWT 放在同一次调用中核对
在 Supabase 中,客户端持有项目密钥并不表示它已经带上登录用户的会话。Supabase 的RLS 指南区分未登录请求的 anon 角色与已登录请求的 authenticated 角色,并说明没有有效用户身份时 auth.uid() 返回 NULL。因此,写给 authenticated 的策略不会放行以 anon 身份到达的请求;以 auth.uid() 对比行内 user_id 的条件,也不会在缺少用户身份时匹配。
核对身份必须发生在那次 SELECT 发出的路径上,而不是只确认用户此前曾在页面上登录。检查该调用是否携带预期的用户访问令牌、会话是否仍有效,以及令牌中的角色和用户标识是否分别符合策略的 TO 范围与行内字段。若查询在客户端恢复登录状态之前发出,后续界面显示已登录也不能说明先前那次请求具有同样身份。
隔离的测试环境中,可以在相同调用位置使用返回 auth.jwt() 的临时 RPC,查看请求实际获得的 role 和 sub,再删除该测试函数。只用数据库会话执行 SET ROLE authenticated,能够辅助检查角色匹配,却不会自动生成该用户的 JWT;依赖 auth.uid() 的策略仍需在带有相应认证上下文的请求中验证。排查记录保留角色和用户标识即可,不必保存完整令牌。
为什么管理连接能读到同一张表
表所有者通常绕过 RLS,超级用户和具有 BYPASSRLS 属性的角色也会绕过。表若设置了 FORCE ROW LEVEL SECURITY,所有者才会受到该表行策略的约束。因此,管理连接读到记录与普通角色收到空结果可以同时成立;比较两次查询时,必须记下各自实际使用的数据库角色。
Supabase 的管理密钥可能使请求以具有旁路权限的 service_role 运行,但还需留意请求是否同时携带用户访问令牌:携带令牌时,会按该登录用户的 RLS 策略执行。管理端查询适合确认数据是否存在,不能代替面向 anon 或 authenticated 请求的策略验证。把管理密钥放进普通客户端,更不会修复用户请求所需的读取规则。
只在测试环境隔离原因
- 准备一条归属已知的测试记录,用目标客户端、目标身份和不带额外过滤条件的 SELECT 重现结果,同时保存返回数据与错误。确认读取的是预期的环境、schema 和表。
- 核对目标表的 RLS 开关、表级 SELECT 授权,以及适用于实际角色的 SELECT 或 ALL 策略。将策略的 USING 条件与测试记录的字段值、请求中的用户标识逐项对照。
- 若仍需区分角色问题与复杂条件问题,只在隔离环境临时为预期角色建立宽松的 SELECT 策略,以 USING (true) 重试同一请求。现有的限制性策略仍可能阻止读取;测试后立即删除临时策略。
- 依据结果修正正式配置,并分别用应获准和应被拒绝的身份验证。正式策略应明确适用角色,并以 USING 表达该角色真正可以读取的行范围。
关闭 RLS 或临时放宽策略可以在隔离环境中帮助定位问题,却不能作为正式修复。若根因是令牌没有随请求送达,应修正会话传递;若根因是策略不适用或条件不匹配,应修正对应的 SELECT 策略,并保留原本需要的访问边界。
相关阅读:
相关文章
订阅我们的新闻通讯
将最新 Web3、AI 和加密货币新闻直接发送到您的邮箱。




