
RLS
AI摘要
北海のAI
今天在给新项目做用户鉴权的时候,由于使用的是Supbase的PostgresSQL数据库并且需要多租户,这里发现了RLS(Row-Level Security),就是数据库中内置的一套“访问控制机制”,核心思想就一句话:让同一 SQL 语句在不同用户执行时,自动返回不同的行集合,且这一过滤发生在数据库内核,任何应用都无法绕过。
一、原理
- 先给表绑定一条策略Policy
- 当用户访问该表时,优化器把 Policy 表达式当作额外 WHERE 条件追加到语句上;值为 true 的行才可见。
- Policy 可以引用系统上下文(当前用户 ID、会话变量、IP、时间…),于是同一张表、同一时刻、不同用户,看到的行集天然不同。
- 在设置的策略的语句中USING 过滤旧行,WITH CHECK 审核新行。
二、泳道图
这里只针对我当前新开发的项目
1、多用户并发登录和操作流程
sequenceDiagram
Note over UA,UB: T1: 用户A登录
UA->>BA: 输入账号密码
BA->>SA: login('admin', 'pwd')
SA->>CA: 获取连接
CA->>DB: SELECT set_config('app.current_user_id', 'user-123')
DB-->>CA: ✅ 设置成功
CA-->>SA: 连接已配置
SA-->>BA: 登录成功
BA-->>UA: 显示管理员界面
Note over UA,UB: T2: 用户B登录
UB->>BB: 输入账号密码
BB->>SB: login('user', 'pwd')
SB->>CB: 获取连接
CB->>DB: SELECT set_config('app.current_user_id', 'user-456')
DB-->>CB: ✅ 设置成功
CB-->>SB: 连接已配置
SB-->>BB: 登录成功
BB-->>UB: 显示普通用户界面
Note over UA,UB: T3: 用户A添加站点
UA->>BA: 点击"添加站点"
BA->>SA: addSite({...})
SA->>CA: INSERT INTO sites
CA->>DB: 执行 INSERT + RLS检查
Note right of DB: 检查 user-123
role = 'admin' ✅
DB-->>CA: ✅ 插入成功
CA-->>SA: 1 row inserted
SA-->>BA: 站点已添加
BA-->>UA: 显示成功提示
Note over UA,UB: T4: 用户B尝试添加站点
UB->>BB: 点击"添加站点"
BB->>SB: addSite({...})
SB->>CB: INSERT INTO sites
CB->>DB: 执行 INSERT + RLS检查
Note right of DB: 检查 user-456
role = 'user' ❌
DB-->>CB: ❌ RLS policy violation
CB-->>SB: Error
SB-->>BB: 操作失败
BB-->>UB: 显示错误提示
Note over UA,UB: T5: 两个用户继续独立操作
par 用户A的操作
UA->>BA: 更新站点
BA->>SA: updateSite({...})
SA->>CA: UPDATE sites
CA->>DB: RLS检查 (user-123)
DB-->>CA: ✅ 成功
and 用户B的操作
UB->>BB: 查看站点
BB->>SB: getSites()
SB->>CB: SELECT * FROM sites
CB->>DB: RLS检查 (user-456)
DB-->>CB: ✅ 返回数据
end
2、单个用户完整会话生命周期
sequenceDiagram
participant U as 👤 用户
participant B as 🌐 浏览器
participant S as 📦 Store (Zustand)
participant SC as 📡 Supabase Client
participant DB as 💾 PostgreSQL + RLS
Note over U,DB: 🚀 阶段1: 页面加载
U->>B: 打开网站
B->>S: init()
S->>S: 读取 localStorage
alt 有会话token
S->>SC: validateSession(userId, token)
SC->>DB: 验证 session
DB-->>SC: ✅ 有效
SC->>DB: SET user context (userId)
Note right of DB: app.current_user_id
= 'user-123'
SC-->>S: 会话有效
S->>SC: 加载用户数据
SC->>DB: SELECT * FROM users WHERE id = ?
Note right of DB: RLS自动检查
允许读取
DB-->>SC: 返回用户数据
SC-->>S: 用户数据
S-->>B: 显示已登录状态
else 无会话token
S->>SC: SET user context (null)
S-->>B: 显示未登录状态
end
Note over U,DB: 🔐 阶段2: 用户登录
U->>B: 输入账号密码
B->>S: login(username, password)
S->>SC: loginUser(username, password)
SC->>DB: 验证凭据
DB-->>SC: ✅ 验证成功 + userId
SC->>DB: SET user context (userId)
Note right of DB: app.current_user_id
= 'user-123'
SC-->>S: 登录成功 + 用户信息
S->>S: 保存到 localStorage
S-->>B: 更新UI状态
B-->>U: 显示登录成功
Note over U,DB: ⚡ 阶段3: 用户操作 (多次)
loop 多次操作
U->>B: 执行操作 (添加/编辑/删除)
B->>S: 调用相应方法
S->>SC: 数据库操作
SC->>DB: SQL + RLS检查
Note right of DB: 自动读取
current_user_id
检查权限
alt 权限允许
DB-->>SC: ✅ 操作成功
SC-->>S: 返回结果
S-->>B: 更新UI
B-->>U: 显示成功
else 权限拒绝
DB-->>SC: ❌ RLS violation
SC-->>S: 返回错误
S-->>B: 显示错误
B-->>U: 提示无权限
end
end
Note over U,DB: 🚪 阶段4: 用户登出
U->>B: 点击登出
B->>S: logout()
S->>SC: logoutUser(userId)
SC->>DB: 清除 session
DB-->>SC: ✅ 已清除
SC->>DB: SET user context (null)
Note right of DB: app.current_user_id
= ''
SC-->>S: 登出成功
S->>S: 清除 localStorage
S-->>B: 重置UI状态
B-->>U: 显示未登录状态
3、RLS 权限检查详细流程
sequenceDiagram
participant U as 👤 用户
participant App as 🖥️ 前端应用
participant API as 📡 Supabase API
participant Pool as 🏊 连接池
participant Conn as 🔌 数据库连接
participant RLS as 🛡️ RLS引擎
participant DB as 💾 数据表
Note over U,DB: 用户已登录,会话变量已设置
U->>App: 点击"添加站点"
App->>App: 检查 userRole (UI层)
alt userRole === 'admin'
App->>API: POST /rest/v1/sites
Note right of App: 前端检查通过
但这不是安全保证
else userRole === 'user'
App-->>U: ❌ 按钮已隐藏
Note right of App: 前端阻止
但可被绕过
end
API->>Pool: 请求数据库连接
Pool->>Conn: 分配连接 #N
Note right of Conn: 连接已有会话变量
app.current_user_id
Conn->>RLS: INSERT INTO sites VALUES (...)
Note over RLS: 🔍 RLS 策略检查开始
RLS->>RLS: 读取会话变量
Note right of RLS: current_setting(
'app.current_user_id'
) → 'user-123'
RLS->>DB: SELECT role FROM users
WHERE id = 'user-123'
DB-->>RLS: role = 'admin' (或 'user')
RLS->>RLS: 评估策略条件
Note right of RLS: 策略: Admins can create sites
条件: role = 'admin'
alt role = 'admin'
RLS->>DB: ✅ 执行 INSERT
DB-->>RLS: 1 row inserted
RLS-->>Conn: ✅ 操作成功
Conn-->>API: 200 OK + data
API-->>App: 站点已创建
App-->>U: ✅ 显示成功提示
else role = 'user'
RLS-->>Conn: ❌ Policy violation
Conn-->>API: 403 Forbidden
API-->>App: RLS error
App-->>U: ❌ 显示权限错误
end
Conn->>Pool: 归还连接
Note right of Pool: 连接回到池中
会话变量保持
三、RLS策略语法
1、基本结构
1 | CREATE POLICY policy_name |
各个简介:
-
1
2# 创建策略的名称,用于标识和管理
CREATE POLICY "admins_can_delete_sites" -- 描述性名称 -
1
2# 应用策略的表
ON sites -- 应用到 sites 表 -
1
2
3
4
5
6# 指定策略应用的操作类型
FOR SELECT -- 只影响 SELECT 查询
FOR INSERT -- 只影响 INSERT 操作
FOR UPDATE -- 只影响 UPDATE 操作
FOR DELETE -- 只影响 DELETE 操作
FOR ALL -- 影响所有操作(默认) -
1
2# 定义哪些行可以被读取或修改
USING (user_id = current_user_id()) -- 只能访问自己的数据 -
1
2# 定义哪些行可以被插入或更新后的状态
WITH CHECK (user_id = current_user_id()) -- 只能插入/更新为自己的数据
2、完整示例
1 | -- 示例 1:用户只能读取自己的订单 |
四、实际应用场景
1、多租户 SaaS 应用
1 | -- 每个租户只能看到自己的数据 |
效果:
1 | -- 租户 A 执行: |
2、社交媒体应用
1 | -- 用户可以看到: |
3、协作文档应用
1 | -- 用户可以访问: |
4、电商平台
1 | -- 卖家只能管理自己的商品 |
本文是原创文章,采用CC BY-NC-SA 4.0协议,完整转载请注明来自北海博客-码海撷贝
评论 ()






