权限
数据库权限决定“哪个角色可以对哪个对象执行什么操作”。正确的权限设计应遵循最小权限原则:应用、报表、迁移和运维使用不同角色,每个角色只获得完成职责所需的能力。
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 都可能影响最终能力。
权限检查清单
- 应用运行角色是否与对象所有者分离;
- 是否只授予完成职责所需的对象和操作;
- 模式
USAGE/CREATE与表权限是否分别审查; - 序列、函数和过程是否有正确授权;
PUBLIC是否保留了不必要权限;- 默认权限是否配置给真正创建对象的角色;
SECURITY DEFINER函数是否固定安全search_path;- 角色继承与转授权链是否可解释;
- RLS 绕过角色是否受到严格控制;
- 是否定期从系统目录验证实际权限,而不是只看迁移脚本。
权限设计的目标不是让每次操作都尽量方便,而是让每个身份的能力清晰、必要、可审计,并在凭据泄露或代码缺陷时限制影响范围。