跳到主要内容

权限

数据库权限决定“哪个角色可以对哪个对象执行什么操作”。正确的权限设计应遵循最小权限原则:应用、报表、迁移和运维使用不同角色,每个角色只获得完成职责所需的能力。

PostgreSQL 使用统一的角色(role)模型。角色可以拥有登录能力,也可以只是权限集合;用户可以理解为带 LOGIN 属性的角色。本文语法与行为以 PostgreSQL 为主。

角色与对象所有者​

创建登录角色:

CREATE ROLE app_runtime LOGIN PASSWORD 'use-a-secret-manager';

真实系统不应把明文密码写入版本库或共享脚本。应通过密钥管理系统、受控初始化流程或云数据库身份机制提供凭据。

创建不登录的权限组:

CREATE ROLE app_readonly NOLOGIN;

把成员加入权限组:

GRANT app_readonly TO analyst_alice;

数据库对象有所有者。对象所有者通常拥有修改、删除和授权该对象的能力,这些所有权能力并不是普通 GRANT ALL 的简单同义词。生产应用运行角色通常不应拥有表,否则一旦被利用就可能绕过预期授权或修改结构。

GRANT 与 REVOKE​

授予查询权限:

GRANT SELECT ON TABLE sales.orders TO app_readonly;

撤销权限:

REVOKE SELECT ON TABLE sales.orders FROM app_readonly;

一次授予多种权限:

GRANT SELECT, INSERT, UPDATE
ON TABLE sales.orders
TO app_runtime;

REVOKE 删除的是某条授权路径。如果角色还通过其他组成员关系、PUBLIC 或对象所有权获得能力,撤销一条授权后仍可能有权限。排查时要查看完整的角色继承与授权来源。

常见对象权限​

不同对象支持不同权限。表上常见的包括:

权限作用
SELECT读取行或列
INSERT插入行
UPDATE修改行或列
DELETE删除行
TRUNCATE清空表
REFERENCES创建引用该表或列的外键
TRIGGER在表上创建触发器
MAINTAIN执行部分表维护操作,具体范围取决于版本

ALL PRIVILEGES 表示该对象类型可授予的所有普通权限,不包含对象所有权的全部能力,也不会自动授予其他对象上的权限。

模式权限与表权限是两层检查​

访问 sales.orders 通常既需要模式的 USAGE,也需要表的相应权限:

GRANT USAGE ON SCHEMA sales TO app_runtime;
GRANT SELECT, INSERT ON TABLE sales.orders TO app_runtime;

模式的 CREATE 允许在该命名空间创建对象,不代表可以读取其中所有表:

GRANT CREATE ON SCHEMA scratch TO developer_role;

不要把不受信任角色可写的模式放入特权会话的 search_path 前部,否则恶意对象可能劫持未限定名称。

表权限与列权限​

可以只允许更新特定列:

GRANT SELECT ON TABLE account.users TO support_agent;
GRANT UPDATE (display_name, phone)
ON TABLE account.users
TO support_agent;

也可以只授予部分列的查询权限:

GRANT SELECT (user_id, display_name, created_at)
ON TABLE account.users
TO support_agent;

列权限适合简单的字段隔离,但复杂查询、表达式、函数和推断攻击仍需审查。需要稳定的公开接口时,可通过只暴露安全列的视图授权,并撤销基础表访问。

序列权限​

向使用序列默认值或某些标识生成方式的表插入时,角色可能还需要序列权限。序列常见权限有 USAGE、SELECT 和 UPDATE,具体含义与允许调用的序列函数相关。

GRANT USAGE ON SEQUENCE sales.orders_order_id_seq
TO app_runtime;

权限脚本应在目标版本中实际测试一次插入,不能只看到表有 INSERT 就认定足够。

函数与过程权限​

函数和过程通常通过 EXECUTE 控制:

GRANT EXECUTE ON FUNCTION
sales.calculate_discount(BIGINT, NUMERIC)
TO app_runtime;

函数重载时,参数类型是对象身份的一部分,授权应写出完整签名。

特别注意 SECURITY DEFINER 函数:它以函数所有者权限执行,类似一个受控的提权入口。必须固定安全的 search_path、验证所有参数、避免动态 SQL 注入,并撤销不必要的 PUBLIC EXECUTE。

普通的 SECURITY INVOKER 函数以调用者权限运行,风险边界更直观。

PUBLIC​

PUBLIC 代表所有角色,包括以后创建的角色。某些对象类型在创建时会默认向 PUBLIC 授予部分权限,例如函数执行权限可能具有宽松默认值。

需要收紧时显式撤销:

REVOKE EXECUTE ON FUNCTION
admin.reset_account(BIGINT)
FROM PUBLIC;

数据库、模式和函数的默认授权会受版本与创建方式影响。安全基线应在新环境中通过查询权限目录实际验证,而不是依赖记忆。

授权选项​

允许接收者把对象权限继续授予别人:

GRANT SELECT ON sales.orders
TO report_admin
WITH GRANT OPTION;

角色成员关系也有管理授权的能力控制,具体语法在新版本 PostgreSQL 中更细分。普通业务角色通常不需要转授权能力,否则权限边界会随人工操作逐渐失控。

撤销授权选项时要考虑依赖授权:如果下游权限由该授权路径产生,RESTRICT 可能拒绝撤销,CASCADE 则会连带撤销。执行前先列出影响对象和角色。

默认权限​

对当前已有表执行批量授权:

GRANT SELECT ON ALL TABLES IN SCHEMA sales
TO app_readonly;

这不会自动覆盖以后新建的表。未来对象应配置默认权限:

ALTER DEFAULT PRIVILEGES
FOR ROLE migration_owner
IN SCHEMA sales
GRANT SELECT ON TABLES TO app_readonly;

默认权限属于“未来由哪个创建者创建的哪类对象”。如果迁移实际由另一个角色执行,上述规则不会生效。常见故障正是配置默认权限的角色与真正建表角色不一致。

序列、函数等对象需要分别配置:

ALTER DEFAULT PRIVILEGES
FOR ROLE migration_owner
IN SCHEMA sales
GRANT USAGE ON SEQUENCES TO app_runtime;

角色继承与 SET ROLE​

登录角色可以通过成员关系获得组角色权限。根据角色属性和 PostgreSQL 版本,权限可能自动继承,也可能需要显式切换:

SET ROLE migration_owner;
RESET ROLE;

推荐让人员或服务登录角色与对象所有者分离,通过受控的 SET ROLE 执行迁移。这样日常连接不会一直携带高权限,审计也更容易区分登录身份和当前执行身份。

权限与行级安全策略​

普通对象权限回答“能否对这张表执行 SELECT 或 UPDATE”,行级安全策略回答“允许操作其中哪些行”。两层都必须通过。

仅有 RLS 策略但没有表 SELECT 权限,仍不能查询;拥有表 SELECT 权限但被 RLS 过滤,也只能看到策略允许的行。超级用户、拥有 BYPASSRLS 的角色以及某些情况下的表所有者可能绕过 RLS,应单独治理。

最小权限角色设计​

一个常见拆分是:

  • app_owner:拥有模式和对象,不允许应用直接登录;
  • app_migrator:通过受控流程切换到所有者角色执行迁移;
  • app_runtime:仅有运行所需的查询与写入权限;
  • app_readonly:供报表或只读运维使用;
  • backup_role:仅有备份所需能力;
  • security_admin:管理角色和授权,不承担日常业务查询。

实际拆分要考虑托管数据库限制、连接池、RLS 和审计系统。

审计权限​

PostgreSQL 的信息模式和系统目录可帮助检查授权。例如 information_schema.table_privileges 能查看部分表权限,dp 是 psql 中的便捷命令。

审计时至少检查:

  • 直接授权;
  • 角色成员关系与继承;
  • PUBLIC 权限;
  • 对象所有者;
  • 默认权限;
  • SECURITY DEFINER 函数;
  • RLS 绕过能力;
  • 不受信任模式的 CREATE 权限。

常见误区​

REVOKE 后角色一定不能访问​

角色可能通过组角色、PUBLIC、所有权或其他授权路径继续拥有权限。

授权所有现有表会覆盖未来表​

不会。未来对象需要 ALTER DEFAULT PRIVILEGES,而且必须针对实际创建对象的角色配置。

应用角色拥有表更方便​

对象所有权带来修改、删除和转授权能力,可能绕过精细权限设计。运行角色与所有者应分离。

只限制表权限就足够​

模式、序列、函数、数据库连接、RLS 和 search_path 都可能影响最终能力。

权限检查清单​

  1. 应用运行角色是否与对象所有者分离;
  2. 是否只授予完成职责所需的对象和操作;
  3. 模式 USAGE / CREATE 与表权限是否分别审查;
  4. 序列、函数和过程是否有正确授权;
  5. PUBLIC 是否保留了不必要权限;
  6. 默认权限是否配置给真正创建对象的角色;
  7. SECURITY DEFINER 函数是否固定安全 search_path;
  8. 角色继承与转授权链是否可解释;
  9. RLS 绕过角色是否受到严格控制;
  10. 是否定期从系统目录验证实际权限,而不是只看迁移脚本。

权限设计的目标不是让每次操作都尽量方便,而是让每个身份的能力清晰、必要、可审计,并在凭据泄露或代码缺陷时限制影响范围。