pg_acm 在 Tallinn 引发共鸣:应用开发者为什么更容易看懂数据库访问控制的痛点

2026-07-03 31 预计阅读时间: 1 分钟
来源: postgr.es AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:9 分钟

Henrietta Dombrovskaya 在 Estonia Postgres User Group 的分享,表面上是一场关于 pg_acm 的技术演讲,真正有意思的地方却在反馈:很多听众在茶歇时直接说“这些问题我都遇到过”。这说明它不是一个只属于 DBA 的冷门话题,而是应用开发者每天都在碰到的权限边界、数据访问和业务规则落地问题。

pg_acm 解决的不是“权限表好不好看”

从分享反馈看,pg_acm 之所以能让应用开发者快速产生共鸣,是因为它触到的是应用系统里最难长期维护的一类逻辑:谁能看什么、谁能改什么、权限如何跟业务状态一起变化。

很多团队一开始会把访问控制写在应用层:

  • Controller 里判断用户角色;
  • Service 里判断记录归属;
  • SQL 查询里拼接 tenant_idowner_id
  • 后台脚本里再复制一遍类似规则。

短期看很快,长期看会变成散落的条件判断。更糟的是,报表、批处理、临时查询、数据修复脚本往往绕过应用层入口,权限模型很容易出现裂缝。

pg_acm 这类思路的价值在于:把访问控制问题拉回数据所在的位置讨论。不是说所有业务规则都必须塞进数据库,而是让数据库承担它天然擅长的部分:靠近数据、统一执行、减少绕路。

为什么应用开发者比许多 DBA 更快“get it”

原文里提到一个很有意思的观察:应用开发者往往比许多 DBA 更快理解这种方法的优势。这并不奇怪。

应用开发者每天面对的是完整业务路径:登录用户、组织关系、页面按钮、API 返回、审计记录、后台任务。对他们来说,访问控制不是抽象的 GRANT SELECT,而是一个具体问题:

这个用户能不能看到这张订单?如果订单进入归档状态,还能不能修改?如果用户属于多个组织,查询结果应该是什么?

传统数据库权限通常更偏对象级别,比如表、视图、函数。而应用访问控制常常是行级、字段级、状态级、关系级。两者之间有一段灰色地带,正是许多系统越做越复杂的来源。

因此,当一个工具或方案试图把这些规则结构化、集中化,并且让 PostgreSQL 参与执行,应用开发者会立刻想到自己代码库里那些“再也不敢碰”的权限判断。

可以这样实践:用 PostgreSQL RLS 模拟同类问题

如果你还没有使用 pg_acm,也可以先用 PostgreSQL 自带的 Row Level Security(RLS)感受这类方案的边界。下面示例不是原演讲中的 pg_acm API,而是一个可复制运行的最小实验,用来模拟“租户只能访问自己的订单”。

运行前需要本机有 PostgreSQL,并能用 psql 连接数据库。把 app.tenant_id 改成你的实际租户标识即可。

DROP TABLE IF EXISTS orders;
DROP ROLE IF EXISTS app_user;

CREATE ROLE app_user LOGIN PASSWORD 'app_user_password';

CREATE TABLE orders (
    id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    tenant_id text NOT NULL,
    amount numeric(12, 2) NOT NULL,
    status text NOT NULL DEFAULT 'open'
);

INSERT INTO orders (tenant_id, amount, status) VALUES
    ('tenant_a', 120.00, 'open'),
    ('tenant_a', 88.50, 'paid'),
    ('tenant_b', 42.00, 'open');

ALTER TABLE orders ENABLE ROW LEVEL SECURITY;

CREATE POLICY tenant_isolation ON orders
    FOR SELECT
    USING (tenant_id = current_setting('app.tenant_id', true));

GRANT SELECT ON orders TO app_user;

然后用同一个连接模拟不同租户访问:

psql "postgresql://app_user:app_user_password@localhost/postgres" <<'SQL'
SET app.tenant_id = 'tenant_a';
SELECT id, tenant_id, amount, status FROM orders ORDER BY id;

SET app.tenant_id = 'tenant_b';
SELECT id, tenant_id, amount, status FROM orders ORDER BY id;
SQL

你会看到同一条 SQL 在不同会话变量下返回不同数据。这就是把访问边界放进数据库执行路径后的直观效果:应用仍然负责认证、会话和业务流程,但数据库可以成为最后一道一致性闸门。

如果继续扩展,可以这样改造:

CREATE POLICY tenant_update_open_orders ON orders
    FOR UPDATE
    USING (
        tenant_id = current_setting('app.tenant_id', true)
        AND status = 'open'
    )
    WITH CHECK (
        tenant_id = current_setting('app.tenant_id', true)
        AND status = 'open'
    );

GRANT UPDATE (amount, status) ON orders TO app_user;

这段策略表达了一个更贴近业务的限制:只能更新当前租户下、仍处于 open 状态的订单。真实项目里,你还需要处理角色继承、管理员例外、审计、迁移脚本和连接池变量污染等问题。

开源版本的现实:反馈越多,越需要工程化收口

原文提到,现场反馈让作者意识到需要修复开源版本里的 bug,并继续扩展即将在 PG.Conf EU 使用的版本。这一点很关键:访问控制工具不是写一个漂亮 DSL 就结束了,它必须接受真实系统的压力测试。

尤其要关注这些边界:

  • 规则是否容易审计,还是只能靠作者解释;
  • 错误配置是否默认拒绝,而不是默认放行;
  • 迁移时旧规则和新规则如何并存;
  • 连接池是否会复用上一个请求的上下文变量;
  • 批处理、报表、管理员后台是否走同一套访问路径;
  • 性能是否能被 EXPLAIN、索引和测试稳定验证。

这也是 Tallinn 这场 meetup 的价值:不是“大家觉得演讲不错”,而是使用者用自己的事故经验验证了问题存在,并愿意试用一个解决方向。

采用建议:先把权限问题画出来,再引入工具

如果你的团队也被访问控制逻辑拖慢,不要急着把所有规则迁到数据库。更稳妥的做法是从一个高风险、规则清晰、数据范围可控的模块开始。

可以按这个清单推进:

  • 选一个实体,例如订单、项目、工单或文档;
  • 写出当前所有读取、更新、导出、后台处理路径;
  • 标出哪些路径依赖应用层判断,哪些路径会直接访问数据库;
  • 用 RLS 或 pg_acm 这类方案做一个最小闭环;
  • 为权限规则补上集成测试和 EXPLAIN 性能检查;
  • 明确管理员、迁移脚本和故障修复流程的例外机制。

pg_acm 这类工具真正吸引人的地方,不是让数据库替代应用,而是让访问控制从“散落在代码里的约定”变成“可以讨论、可以测试、可以演进的系统部件”。这也是为什么一场看似小众的 Postgres 演讲,会让现场应用开发者不断点头。


相关推荐