6.3 权限体系与行级安全 RLS


6.3 权限体系与行级安全 RLS

本节摘要:PostgreSQL 的权限是三层结构:角色(可继承、可组合)持有权限,权限按"库 → 模式 → 表 → 列 → 行"逐层收窄。行级安全(RLS)在引擎层给表挂策略表达式,查询自动拼上过滤条件,多租户隔离不再依赖应用代码的自觉。RLS 默认对表所有者不生效,这个口子必须知晓并封堵。

角色与授权的最小闭环

-- 一个只读报表角色,可被多人"成为" CREATE ROLE report_reader NOLOGIN; GRANT USAGE ON SCHEMA analytics TO report_reader; GRANT SELECT ON analytics.daily_sales TO report_reader; -- 应用账号继承它的权限 CREATE ROLE app_report LOGIN PASSWORD '...'; GRANT report_reader TO app_report;

要点三条:角色可以继承角色,形成权限树;PUBLIC 是全员角色,回收默认权限要显式对 PUBLIC 操作;数据库、模式、表是三道闸,缺一不可——最常见的失误是给了表权限却忘了模式上的 USAGE。

默认权限体系是"全给再收回",新表的授权要跟得上建表节奏:

-- 让今后新建的表自动可读,省去逐张授权 ALTER DEFAULT PRIVILEGES IN SCHEMA analytics GRANT SELECT ON TABLES TO report_reader;

RLS:把 WHERE 写进引擎

传统多租户靠"每条查询都记得加 tenant_id 条件",漏一条就泄露。RLS 把这条防线钉在表上:

ALTER TABLE project ENABLE ROW LEVEL SECURITY; CREATE POLICY tenant_isolation ON project USING (tenant_id = current_setting('app.tenant_id')::int);

此后任何查询(包括应用拼错的 SQL)都会被引擎自动追加过滤,读不到也改不到别的租户的行:

SET app.tenant_id = '42'; SELECT count(*) FROM project; -- 只数出租户 42 的行

策略有 USING(读过滤)与 WITH CHECK(写校验)两半,可以分开设:

CREATE POLICY own_rows ON project FOR UPDATE USING (owner = current_user) -- 只能改自己的行 WITH CHECK (owner = current_user); -- 也不能改成别人的

图:权限收窄的五个层级

图:权限收窄的五个层级

⚠️ 常见坑:RLS 默认不约束表的所有者与超级用户——用超级用户账号跑应用等于裸奔。正确姿势是应用使用普通角色,或对表显式加 FORCE ROW LEVEL SECURITY 让所有者也受策略管辖。

RLS 性能:策略即谓词,索引即药方

RLS 策略在查询规划期被拼进 WHERE,因此它的性能就是谓词的性能。策略表达式用不上索引时,每条查询都是全表过滤:

-- 策略谓词里的表达式若与索引列不完全一致,索引失效 CREATE POLICY tenant_idx_unfriendly ON project USING (tenant_id = current_setting('app.tenant_id')::int); -- 配套表达式索引让谓词可走索引 CREATE INDEX ON project ((current_setting('app.tenant_id')::int), tenant_id);

更省心的做法是把取租户封装成函数并标记稳定(STABLE),再对 tenant_id 建普通索引——规划器把稳定函数当常量求值后,普通索引即可命中。验证手段就是 EXPLAIN:

SET app.tenant_id = '42'; EXPLAIN SELECT count(*) FROM project; -- 期望看到 Index Scan / Index Only Scan 而非 Seq Scan + Filter

RLS 上线前的验收清单应当包含这一步:以真实应用角色的权限跑一遍核心查询的计划,确认策略没有把索引扫变成全表扫。

策略组合与默认拒绝

多条策略并存时默认是"或"语义(PERMISSIVE):任一策略放行即放行。要做"白名单再减黑名单"的精细控制,用 RESTRICTIVE 策略:

-- 基础放行:本租户可见 CREATE POLICY p_tenant ON project USING (tenant_id = current_setting('app.tenant_id')::int); -- 追加收紧:且行状态不是已归档 CREATE POLICY r_not_archived ON project AS RESTRICTIVE USING (status <> 'archived');

两条叠加后,可见集是交集——RESTRICTIVE 永远只能收窄不能放宽。另一个必须刻进设计的默认:启用 RLS 但一条策略都没建的表,对普通角色完全不可见(默认拒绝),而对所有者仍然全可见。这两条规则组合出一个常见翻车现场:开发用所有者账号测试一切正常,应用用普通账号上线后查无数据。测试矩阵必须包含"非所有者角色"这一行。

排错案例:超级用户测试通过、上线泄露

某多租户系统的 RLS 上线半年后被租户报告看到他人数据。复盘发现两层叠加:排障脚本用超级用户跑,RLS 对其不生效,测试结论失真;一张后加的表忘了 ENABLE ROW LEVEL SECURITY,成了敞门。修复动作三条:全部业务表启用 FORCE ROW LEVEL SECURITY(所有者也受管);应用账号降为普通角色,超级用户仅限运维登录且禁止业务库长连;把"新表是否启用且强制 RLS、策略是否有配套索引"写进建表代码评审清单。RLS 的安全边界完全由例外清单决定——所有者、超级用户、未启用的表,每个例外都是一个需要流程封堵的门。

本节要点回顾

  • 角色树 + 默认权限:授权一次,新表自动继承
  • 模式 USAGE 别漏:三道闸缺一不可
  • RLS 双半策略:USING 管读,WITH CHECK 管写
  • 所有者是豁免口:FORCE 开关或专用低权账号封堵

作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U