在现代 SaaS 应用中,细粒度授权是确保数据安全和合规的核心需求。传统的角色-based 访问控制 (RBAC) 虽简单,但难以处理复杂的多租户场景,如组织与仓库的层次关系。关系-based 访问控制 (ReBAC) 提供更灵活的解决方案,能基于实体间关系动态评估权限,例如 Google Drive 中的文件夹继承机制。然而,引入如 OpenFGA 等外部授权引擎往往带来数据库同步的复杂性和运维开销。本文探讨一种纯 Postgres 实现 ReBAC 的方法——pgFGA,通过关系代数、视图和触发器实现政策存储、评估和元组级访问控制,无需外部依赖。这种方法充分利用 Postgres 的原生能力,简化架构并提升一致性。
ReBAC 的核心在于关系元组(relationship tuples),形式为 {user, relation, object},如 {user:Isaac, member, org:Rover} 表示用户 Isaac 是 Rover 组织的成员。OpenFGA 基于 Google Zanzibar 论文,提供授权模型定义关系继承,例如组织成员自动获得仓库读权限。但在生产环境中,OpenFGA 需要独立的 Postgres 实例,每次主数据库变更(如用户加入组织)都需同步元组到 OpenFGA。这导致事务一致性问题、级联删除复杂,以及手动维护的负担。Rover 团队在实践中发现,这种分离增加了运维成本,尤其在小团队中不经济。
pgFGA 通过将授权逻辑内嵌到主数据库中解决这些痛点。其架构包括三个关键组件:授权模型表、关系元组视图和递归权限检查函数。首先,authz_model 表存储模型定义,使用 TypeID 作为主键,支持版本化管理。表结构如下:
CREATE TABLE "authz_model" (
"id" text PRIMARY KEY DEFAULT typeid_generate_text('authz') NOT NULL,
"schema_version" bigint NOT NULL,
"entity_type" text NOT NULL,
"relation" text NOT NULL,
"subject_type" text,
"implied_by" text,
"parent_relation" text
);
此表对应 OpenFGA 的 DSL 模型,例如定义组织角色继承:owner 隐含 admin,admin 隐含 member。插入模型时,可借助 OpenFGA CLI 的 JSON 输出作为胶水代码,确保兼容性。
其次,authz_relationship 视图动态生成元组,避免手动同步。视图从现有表(如 organization_user)union 所有关系,例如:
CREATE VIEW authz_relationship AS
SELECT 'user' AS user_type, ou.user_id AS user_id, ou.role AS relation,
'organization' AS object_type, ou.organization_id AS object_id
FROM organization_user ou
UNION ALL
SELECT 'repository' AS user_type, r.id AS user_id, 'organization' AS relation,
'organization' AS object_type, r.organization_id AS object_id
FROM repository r;
通过外键和级联删除,主表变更自动反映到视图中,支持多租户隔离(如每个组织独立命名空间)。为优化性能,可将视图物化为物化视图(materialized view),并在相关表上添加触发器刷新:
CREATE MATERIALIZED VIEW authz_relationship_mv AS
SELECT * FROM authz_relationship;
CREATE TRIGGER refresh_authz_mv
AFTER INSERT OR UPDATE OR DELETE ON organization_user
FOR EACH STATEMENT EXECUTE FUNCTION refresh_materialized_view('authz_relationship_mv', NULL);
这确保元组实时更新,同时利用 Postgres 的触发器机制处理并发。
最后,check_permission 函数是评估核心,使用 PL/pgSQL 递归遍历模型和元组图。函数签名支持版本指定:
CREATE OR REPLACE FUNCTION check_permission(
model_version bigint DEFAULT NULL,
user_type text,
user_id text,
relation text,
object_type text,
object_id text
) RETURNS boolean AS $$
$$ LANGUAGE plpgsql;
实现中,函数优先检查直接元组,若无则递归查询 implied_by 和 parent_relation,形成权限传播路径。例如,查询用户对仓库的 read 权限时,若用户是组织 member,则继承成功。递归深度受 Postgres 配置限制(default_work_mem 等),适合中小规模查询。
在实施 pgFGA 时,以下参数和清单确保落地可靠:
-
模型管理:使用环境变量设置当前 schema_version(如 PROD_SCHEMA_VERSION=1)。部署时运行迁移脚本验证模型一致性,避免 ULID 随机性问题。
-
性能调优:设置 statement_timeout=5000ms 限制长查询;索引 authz_model (entity_type, relation) 和视图 (user_type, user_id, object_type, object_id)。对于高并发,使用读副本分离查询。
-
事务集成:在 DAL 中包裹权限检查,如 BEGIN; INSERT 用户; PERFORM check_permission(...); COMMIT; 利用 Postgres 事务原子性。
-
监控要点:追踪 check_permission 执行时间(EXPLAIN ANALYZE),警报 >100ms;监控模型版本漂移(定期校验视图与模型匹配);审计日志记录权限拒绝事件。
-
回滚策略:版本化模型支持热更新,若新模型失效,回滚到上一版本并重建视图。测试环境中使用内存表模拟,避免生产影响。
-
扩展清单:支持条件元组时,添加环境属性列到模型表;多租户隔离通过 row-level security (RLS) 策略实现,如 ALTER TABLE authz_model ENABLE ROW LEVEL SECURITY; CREATE POLICY org_isolation ON authz_model USING (object_type = current_setting('app.current_org'))。
pgFGA 的优势在于零额外依赖,降低延迟(单数据库查询 <10ms)和成本(无需独立服务)。在 Rover 的多租户场景中,它无缝处理组织-仓库继承,确保成员仅访问所属资源。限制包括视图维护的手动性(未来可自动化生成)和复杂模型的递归开销,但对大多数应用已足够。相比外部引擎,pgFGA 更易于小团队维护,并与 Postgres 生态深度集成。
总之,这种纯 Postgres ReBAC 实现证明了关系数据库在授权领域的潜力。通过视图和触发器的巧用,它实现了高效的政策评估和元组控制,推动了安全系统的简化。
资料来源:
- Rover Substack: "How we rewrote OpenFGA in pure Postgres" (2025-03-20)
- GitHub: DocumaticAI/pgfga (MIT 许可,开源实现)