结合 Auth 的用户隔离:私有数据模式


文档摘要

结合 Auth 的用户隔离:私有数据模式 策略的语法掌握后,最核心的应用场景就是「用户数据隔离」——让每个用户只能访问自己的数据。本节讲解如何用 RLS 实现这一目标,以及「行归属标识」这一关键设计。 用户隔离的本质 用户隔离要解决的问题是:同一张表里混着所有人的数据,如何让用户 A 只看到 A 的、用户 B 只看到 B 的? 答案就藏在第 4 章——每个用户都有唯一的 id(UUID)。只要每行数据都标注「它属于哪个用户」,策略就能据此判断:只有行的归属者 id 与当前请求者 id 相等时,才放行。 关键:行上要有归属标识 实现隔离的前提是:业务表必须有标识「这行属于谁」的列。通常是一列 (UUID),引用认证系统的用户 id。 有了 列,策略就能用 来过滤。

结合 Auth 的用户隔离:私有数据模式

策略的语法掌握后,最核心的应用场景就是「用户数据隔离」——让每个用户只能访问自己的数据。本节讲解如何用 RLS 实现这一目标,以及「行归属标识」这一关键设计。

用户隔离的本质

用户隔离要解决的问题是:同一张表里混着所有人的数据,如何让用户 A 只看到 A 的、用户 B 只看到 B 的?

答案就藏在第 4 章——每个用户都有唯一的 id(UUID)。只要每行数据都标注「它属于哪个用户」,策略就能据此判断:只有行的归属者 id 与当前请求者 id 相等时,才放行

关键:行上要有归属标识

实现隔离的前提是:业务表必须有标识「这行属于谁」的列。通常是一列 user_id(UUID),引用认证系统的用户 id。

orders 表: id | user_id(归属者)| amount | ... ---|------------------|--------|---- A | 用户A的UUID | 100 | ... B | 用户B的UUID | 200 | ...

有了 user_id 列,策略就能用 user_id = 当前用户 id 来过滤。因此设计表时(第 2 章)就要为需要隔离的表预留归属列——这是 RLS 能发挥作用的基础。

标准的私有数据策略

以订单表为例,一套完整的「私有数据」策略覆盖四个命令:

  • SELECT:只读 user_id 等于本人的行。
  • INSERT:插入时新行的 user_id 必须等于本人(WITH CHECK)。
  • UPDATE:只能改本人的行,且改完 user_id 仍须是本人。
  • DELETE:只能删本人的行。

合并起来,可用一条 ALL 策略表达(概念示意):

-- 用户只能读写属于自己的订单 CREATE POLICY "订单私有" ON orders FOR ALL TO authenticated USING (user_id = auth.uid()) WITH CHECK (user_id = auth.uid());

auth.uid() 返回当前登录用户的 id;USING 管读/改/删(针对已存在的行),WITH CHECK 管插入与更新后的新行。两端都用 user_id = 当前用户 id 锁定,用户就只能触碰自己的数据。

防止「冒名写入」

特别注意 INSERT 的 WITH CHECK。如果只写了 SELECT/UPDATE/DELETE 策略却忘了 INSERT,或 INSERT 策略没有 WITH CHECK,用户就可能插入一行 user_id 是别人的数据——冒充他人。完整的私有策略必须在 WITH CHECK 中强制 user_id = 当前用户 id

一个常见错误:前端用当前用户填表,开发者以为「前端传的就是对的」,于是不给 INSERT 加 WITH CHECK。但客户端的任何值都可被篡改,安全必须以服务端(数据库)的强制校验为准。

自动填充 user_id:触发器

为了减少出错与简化前端,常用触发器在插入时自动把 user_id 填为当前用户,而不依赖前端传入。这样:

  • 前端提交时无需传 user_id,数据库自动填正确值。
  • 即使前端篡改 user_id,触发器也会覆盖为真实身份。
  • 配合 WITH CHECK 形成双重保险。

触发器逻辑:在 INSERT 之前,把 NEW 行的 user_id 设为当前用户 id。这是 Supabase 中非常常见的模式。

多种归属关系的处理

有些表的归属不止「直接拥有者」一种,例如:

  • 协作场景:文档属于创建者,但也共享给协作者。此时除了 owner_id,还需一张「协作者」关联表,策略里用子查询判断「当前用户是 owner 或在协作者列表中」。
  • 层级归属:订单项属于订单,订单属于用户。订单项本身没有直接的 user_id,需通过 order_id 关联到订单再找到用户。策略中用 EXISTS 子查询跨越层级判断。
-- 概念示意:订单项通过所属订单间接归属用户 CREATE POLICY "订单项随订单隔离" ON order_items FOR SELECT TO authenticated USING ( EXISTS ( SELECT 1 FROM orders o WHERE o.id = order_items.order_id AND o.user_id = auth.uid() ) );

这类「间接归属」在规范化设计中很常见,理解子查询在策略中的用法就能处理任意层级的归属关系。

隔离与查询性能

带 RLS 的查询,数据库会在内部把策略表达式附加到 WHERE 子句上,相当于自动加了一层过滤。需要注意:

  • 归属列(如 user_id)应有索引,否则逐行评估会变慢。
  • 跨表 EXISTS 子查询也要确保关联列有索引。
  • 复杂策略表达式应尽量简洁,避免在策略里做重计算。

小结

用户隔离的核心是「行上的归属列 + 基于 id 的策略比较」:表必须有 user_id(或可推导的归属),策略用 user_id = 当前用户 id 限定读写,并用触发器与 WITH CHECK 防止冒名。掌握这一模式,你就实现了 Supabase 安全体系的灵魂。下一节归纳几类常见的访问模式,作为实战模板。


发布者: 作者: 灏天文库 转发
评论区 (0)
U