策略设计与编写:把规则精确表达出来


文档摘要

策略设计与编写:把规则精确表达出来 理解原理后,本节进入策略的具体编写。你将学会策略的语法结构、如何针对不同命令(查询/插入/更新/删除)编写策略,以及几个关键的用户身份函数。 策略的基本结构 一条策略通常包含以下要素: 名称:便于识别策略意图,如「用户只能读自己的行」。 作用的表:策略挂在哪张表上。 覆盖的命令:SELECT / INSERT / UPDATE / DELETE / ALL(全部)。 针对的角色:哪些角色适用(匿名、认证用户、或全部)。 布尔表达式:决定某行是否放行的核心逻辑。 概念上,一条策略可读作:「对于在某表上执行某命令的某角色,当布尔表达式为真时,允许操作这一行」。 关键身份函数 策略表达式中,最常用的是几个读取当前用户身份的函数。

策略设计与编写:把规则精确表达出来

理解原理后,本节进入策略的具体编写。你将学会策略的语法结构、如何针对不同命令(查询/插入/更新/删除)编写策略,以及几个关键的用户身份函数。

策略的基本结构

一条策略通常包含以下要素:

  • 名称:便于识别策略意图,如「用户只能读自己的行」。
  • 作用的表:策略挂在哪张表上。
  • 覆盖的命令:SELECT / INSERT / UPDATE / DELETE / ALL(全部)。
  • 针对的角色:哪些角色适用(匿名、认证用户、或全部)。
  • 布尔表达式:决定某行是否放行的核心逻辑。

概念上,一条策略可读作:「对于在某表上执行某命令的某角色,当布尔表达式为真时,允许操作这一行」。

关键身份函数

策略表达式中,最常用的是几个读取当前用户身份的函数。这里用通用概念描述它们的语义(具体函数名以 Supabase 提供的为准):

  • 「当前用户 id」:返回当前登录用户的 UUID,匿名用户返回空。这是判断「行是否属于当前用户」的核心——通常把行的 user_id 列与它比较。
  • 「是否已登录」:返回当前是否为认证用户(true / false)。用于区分「公开可读」与「需登录」。
  • 「当前角色」:返回当前会话的角色(匿名 / 认证用户 / 服务角色),用于角色级判断。

掌握这三个函数,就能表达绝大多数策略。

针对各类命令编写策略

SELECT 策略:控制「能读到哪些行」

最常见的策略。表达式决定某行是否出现在查询结果中。

语义示例:「只返回 user_id 等于当前用户 id 的行」。

-- 概念示意:允许认证用户查询属于自己的行 CREATE POLICY "用户只能读自己的行" ON orders FOR SELECT TO authenticated USING (user_id = auth.uid());

USING 子句对每行评估,为真则该行可见。

INSERT 策略:控制「能插入哪些行」

INSERT 策略防止用户插入「不属于自己」的数据。它有一个特别的检查子句(WITH CHECK),在插入时对新行求值。

语义示例:「插入订单时,新行的 user_id 必须等于当前用户 id」。

CREATE POLICY "用户只能为自己下单" ON orders FOR INSERT TO authenticated WITH CHECK (user_id = auth.uid());

INSERT 策略极其重要——否则用户 A 可以往订单里写 user_id 为用户 B 的行,冒充他人。务必让 WITH CHECK 锁定 user_id 与当前用户一致。

UPDATE 策略:控制「能改哪些行」

UPDATE 策略既要控制「能改哪些已存在的行」,又要控制「改成什么样」。因此它有两个子句:

  • USING:哪些旧行允许被更新。
  • WITH CHECK:更新后的新行必须满足什么。

语义示例:「用户只能更新自己的行,且更新后 user_id 仍必须是本人」。

CREATE POLICY "用户只能改自己的行" ON orders FOR UPDATE TO authenticated USING (user_id = auth.uid()) WITH CHECK (user_id = auth.uid());

两层检查确保用户既能改自己的行,又不能借更新把数据「转移」给别人。

DELETE 策略:控制「能删哪些行」

DELETE 策略只关心 USING:哪些行允许被删除。

CREATE POLICY "用户只能删自己的行" ON orders FOR DELETE TO authenticated USING (user_id = auth.uid());

ALL 策略:一条覆盖所有命令

如果想用同一条规则覆盖所有命令,可用 FOR ALL,并在 USINGWITH CHECK 中分别表达读改与写入条件。适合规则统一的简单表。

角色维度:匿名 vs 认证用户

策略还要指明对哪些角色生效

  • anon:匿名用户(未登录)。
  • authenticated:已登录用户。
  • public:所有角色(含匿名与认证)。

例如公开文章表,读取策略可用 TO public(所有人可读),写入策略用 TO authenticated(只有登录用户能写)。这种按角色分层是常见做法。

策略的组合与默认拒绝

回顾几个要点:

  • 同一命令可有多条 permissive 策略,任一满足即放行。
  • 如果某命令完全没有策略覆盖,默认拒绝(前提是该表已启用 RLS)。
  • 因此务必检查每张表、每个命令是否都有预期的策略,避免「忘了给 DELETE 加策略」这类漏洞。

编写策略的工作流

  1. 先启用目标表的 RLS(如果尚未启用)。
  2. 明确这张表「谁能读、读哪些行」「谁能写、写哪些行」。
  3. 为每个命令编写策略,指定角色与表达式。
  4. 分别用不同身份(匿名、用户 A、用户 B)测试,验证放行与拒绝都符合预期。

测试是关键。策略写错往往不会报错,而是悄悄放行或误拒。养成「写完必测」的习惯。

小结

策略由命令、角色与布尔表达式组成,通过 USINGWITH CHECK 控制读写;身份函数提供当前用户 id、登录状态与角色;INSERT/UPDATE 的 WITH CHECK 尤为关键,能防止冒名写入。下一节我们把这些策略与第 4 章的用户身份结合,实现真正的数据隔离。


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