六维教程

Supabase 行级安全策略 RLS

这是全教程最重要的一篇。RLS(Row Level Security,行级安全策略)决定谁能对哪些行做什么操作。前面学的所有内容,不配上 RLS 都是裸奔。很多新手跳过这一篇,结果产品上线后被用户扒数据,这是 Supabase 最不能省的知识点。

没有 RLS 的后果

回想第五篇,anon key 是公开的,任何人都能拿到。如果你的表没有 RLS,任何人打开浏览器控制台就能执行

// 查看所有用户的资料
await supabase.from('profiles').select('*')
// 删除所有数据
await supabase.from('profiles').delete().neq('id', '00000000-0000-0000-0000-000000000000')

这不是理论风险,只要你的网站被任何人访问过,就有人在裸奔的数据库上试过。记住一句话,没有 RLS 的表就是公开数据库。

RLS 是什么

RLS 是 PostgreSQL 自带的安全机制,Supabase 让它变成了产品的主打功能。可以把它想象成办公大楼的保安,每个人刷卡只能进自己的楼层,电梯会拦截没权限的人。

角色 电梯比喻 数据库对应
保安 控制谁能进哪层 RLS 策略
员工卡 身份凭证 用户的 access_token
楼层 允许访问的数据行 策略里筛选出的行

策略的本质是给每种操作定义一个条件,满足条件的行才放行。

开启 RLS

开启 RLS 本身很简单,一句 SQL

alter table public.profiles enable row level security;

开启后所有操作立刻被拦截,即使是你自己写的表,查询也会返回空数组。这不是坏了,是保安刚上岗,还没配任何通行规则。

给 profiles 表开完 RLS 后再执行第四篇的查询,结果一定是空。体会到这种”突然什么都查不到”的感觉,就理解了 RLS 的威力。

auth.uid() 是谁

写策略前先认识一个函数,auth.uid() 返回当前请求的用户 ID。未登录的请求返回 null,登录后返回 access_token 里解析出的用户 ID。

auth.uid() 就是保安读卡器,它从请求头里读身份,完全由 Supabase 托管,你不需要自己存任何会话状态。

策略四要素

一条完整的策略由四个部分组成

要素 说明
策略名 描述这条规则是干什么的
操作 for select / insert / update / delete
角色 默认 public,即所有角色,一般保持默认
条件 using 和 with check 两套

using 决定哪些行可见,with check 决定写入的行是否合法。select 和 delete 只关心 using,insert 只关心 with check,update 两个都关心。update 的 using 管”能改哪些行”,with check 管”改完的结果合不合法”。

profiles 表完整策略

为 profiles 表写三组策略,覆盖一个用户对自己资料的完整操作

-- 用户只能读取自己的资料
create policy "用户读取自己的资料"
on public.profiles for select
using (auth.uid() = id);

-- 用户只能插入自己的资料
create policy "用户插入自己的资料"
on public.profiles for insert
with check (auth.uid() = id);

-- 用户只能更新自己的资料,且不能把 id 改成别人
create policy "用户更新自己的资料"
on public.profiles for update
using (auth.uid() = id)
with check (auth.uid() = id);

执行完这三条,回前端用登录用户的会话查 profiles,只能看到自己那一行,改别人报错,改自己成功。

id 列与用户 ID 对应的关系是整套策略的关键。用自己资料表的 id 等于 auth.uid() 来表达”我的数据”。

允许所有人读的公开数据

不是所有表都私有。比如文章列表,人人都能看,只要读的权限放开,写仍然受控

-- 任何人可读已发布文章
create policy "公开读文章"
on public.posts for select
using (true);

-- 只有作者能写
create policy "作者管理自己的文章"
on public.posts for all
using (auth.uid() = user_id)
with check (auth.uid() = user_id);

using (true) 表示不设条件,所有行可见。for all 是 select、insert、update、delete 四合一写法。

管理员的特殊通道

RLS 拦截所有匿名请求,但 service_role key 是特例。用 service_role key 发起的请求携带的角色是 service_role,默认绕过所有策略。

这解释了第二篇为什么强调 service_role key 不能出现在前端。它就像万能钥匙,只能放在自己的服务器上。

常见误区

误区 正解
登录了就能查所有数据 错,RLS 是独立的一层,登录只解决你是谁
在 Dashboard 里能查,前端查不到 正常,Dashboard 走的是特权通道
策略名随便起 建议起中文名,方便团队理解每条策略的意图
RLS 只影响前端 错,任何通过 PostgreSQL 连接发起的请求都受策略约束

检查清单

上线前对照自查

  1. 每张业务表都执行了 enable row level security
  2. 每张表都有明确的 select、insert、update、delete 策略
  3. 需要公开的数据明确写了 using (true)
  4. service_role key 只存在服务端

RLS 是 Supabase 安全体系的灵魂,这篇内容值得反复读。下一篇学习多表关联,把数据结构设计得更合理。

下一篇 Supabase 数据关联查询

上一篇
Supabase 第三方登录
下一篇
Supabase 数据关联查询