第十八章 多租户架构设计 - 第一节:行级安全策略(RLS)
文章目录
第十八章 多租户架构设计
第一节 行级安全策略(RLS)与 Row-Level Security
目标:理解多租户架构中数据隔离的核心挑战,并掌握 PostgreSQL 提供的强大功能——行级安全策略(Row-Level Security, RLS),以在数据库层面实现精细、安全的数据访问控制。
**多租户(Multi-Tenancy)**是一种软件架构模式,它允许单个应用实例服务于多个不同的客户(租户)。例如,一个 SaaS 平台,每个注册的公司都是一个独立的租户。
在多租户架构中,最大的挑战之一就是数据隔离:如何确保租户 A 绝对无法访问到租户 B 的数据?
有多种实现数据隔离的策略(如独立数据库、独立 Schema),但最常用、最具扩展性的是共享数据库、共享 Schema,通过一个 tenant_id 列来区分数据的模式。然而,这种模式完全依赖于应用层代码来保证数据隔离。如果应用代码出现一个 bug(例如,在 WHERE 子句中忘记添加 WHERE tenant_id = ...),就可能导致灾难性的数据泄露。
行级安全策略(Row-Level Security, RLS) 将数据隔离的责任从应用层下沉到了数据库层,提供了一种更强大、更可靠的安全保障。
RLS 的工作原理
RLS 允许你为一个表定义一个安全策略(Policy)。这个策略本质上就是一个会返回布尔值的 SQL 表达式。当一个用户尝试对该表进行 SELECT, INSERT, UPDATE, DELETE 操作时,PostgreSQL 会自动地、强制地将这个策略表达式作为 WHERE 子句(对于读操作)或 WITH CHECK 选项(对于写操作)应用到用户的查询上。
如果策略表达式返回 true,则允许访问该行;如果返回 false 或 NULL,则该行对用户完全不可见,就像它不存在一样。
实战:为 SaaS 应用的 documents 表启用 RLS
场景
我们有一个 documents 表,存储了所有租户的文档。我们需要确保普通用户只能看到和操作自己租户的文档。
第一步:准备表和数据
| |
第二步:创建数据库用户
为了演示,我们创建两个代表不同应用用户的数据库角色。
| |
此时,如果 app_user_alice 连接到数据库,她可以 SELECT * FROM documents 并看到所有租户的数据,这是不安全的。
第三步:启用 RLS 并定义策略
1. 在表上启用 RLS
| |
重要:一旦启用 RLS,默认情况下所有访问都会被拒绝(默认拒绝策略),直到你创建至少一个允许访问的策略。此时,即便是表的所有者也无法看到任何数据。
2. 创建一个策略 策略的核心是利用 PostgreSQL 的会话配置参数。我们可以在应用连接到数据库后,设置一个代表当前租户 ID 和用户 ID 的参数。
| |
USING (...): 定义了用于读操作(SELECT)的过滤条件。WITH CHECK (...): 定义了用于写操作(INSERT,UPDATE)的检查条件。
第四步:验证 RLS 效果
现在,让我们模拟应用用户的操作。
模拟 Alice (租户 ‘acme’) 的会话:
| |
结果: 只会返回 ‘acme’ 租户的两条文档,Globex 的文档对她完全不可见。
| |
尝试插入非法数据:
| |
结果:
查询会失败,并报错 new row violates WITH CHECK OPTION for "documents",因为 WITH CHECK 条件阻止了这个操作。
更精细的控制
RLS 策略可以非常灵活。例如,我们可以增加一个策略,允许用户只能修改自己拥有的文档。
| |
(假设我们也设置了 app.current_user_id 参数)
当多个策略存在时,它们会用 OR 逻辑组合起来。
📌 小结
- RLS 是实现数据库层多租户数据隔离的黄金标准。它提供了一种强大而可靠的安全机制,可以防止因应用层代码漏洞导致的数据泄露。
- 核心原理:通过
CREATE POLICY定义访问规则,PostgreSQL 会自动将这些规则强制应用到所有查询上。 - 实现方式:通常结合会话配置参数(如
current_setting('app.current_tenant_id'))来动态地、安全地将当前用户的身份信息传递给策略表达式。 - 默认拒绝:启用 RLS 后,必须显式创建
ALLOW策略,否则所有访问都会被拒绝。
将安全策略下沉到离数据最近的地方,是构建健壮、可信的 SaaS 应用的关键一步。RLS 让 PostgreSQL 在多租户架构设计中,拥有了与许多专业数据库相媲美的安全能力。
