CREATE / DROP ROLE
角色(role)是 PostgreSQL 权限体系的基石。与其他数据库把「用户」和「组」做成两类不同实体不同,PostgreSQL 从 8.1 版本起就把这两者统一成了一种东西:角色。一个角色可以扮演用户(可以登录)、扮演组(接收权限再分发给别人),或者两者兼备——这取决于你给它设置了哪些属性。
理解角色是理解一切权限问题的前提。前面学习读写分离时,我们已经在连接串里用过 user=xxx,那个「user」本质上就是一个带 LOGIN 属性的角色。本节将从 CREATE ROLE 出发,把角色的完整属性、生命周期(创建 → 修改 → 删除)、内置预设角色以及角色相关的系统视图逐一讲透。
这一节只解决「角色本身」的问题:角色是什么、怎么造、怎么删。至于权限如何授予与回收(GRANT / REVOKE)、成员关系如何继承,属于后面几页的主题。先把地基打好,后面的墙才不会歪。
前置知识
建议先阅读:读写分离与中间件
基础概念
在动手写 SQL 之前,先把角色体系里的核心术语厘清。下面这张表会贯穿整个「安全与权限」章节。
| 概念 | 英文 / 记法 | 说明 |
|---|---|---|
| 角色 | role | 集群级实体,可以拥有对象、接受授权、被授予成员关系 |
| 登录角色 | login role | 带 LOGIN 属性的角色,即通常意义上的「用户」 |
| 组角色 | group role | 不带 LOGIN 的角色,仅用于承载权限并分发给成员 |
| 角色属性 | role attributes | SUPERUSER、CREATEDB、CREATEROLE 等作用于角色本身的特权 |
| 成员关系 | role membership | GRANT role_a TO role_b 建立的「b 是 a 的成员」关系 |
| 预设角色 | predefined roles | pg_monitor、pg_read_all_data 等内置角色,由 GRANT 分发 |
| 会话角色 | session role | 当前连接最初使用的登录角色,即 session_user |
| 当前角色 | current role | 当前正在执行语句的有效角色,即 current_user |
角色即用户,角色即组
PostgreSQL 官方文档对角色的定义只有一句话:角色是可以拥有数据库对象、可以拥有数据库特权的实体;它可以是「用户」、是「组」,或两者都是。这句话包含了三层意思:
- 角色拥有对象:表、序列、函数、模式、数据库……谁创建的,属主(owner)就是谁。属主天然拥有该对象的全部可授予权限,以及删除、修改它的权利。
- 角色接受授权:权限(
SELECT、INSERT、CONNECT等)可以授给任何角色;成员关系也可以授给任何角色。 - 用户与组只是用法,不是类型:
CREATE USER不过是CREATE ROLE ... LOGIN的别名(历史写法),CREATE GROUP不过是CREATE ROLE ... NOLOGIN的别名。同一个角色完全可以既登录又充当组。
正是这种「单一实体」的设计,让 PostgreSQL 的权限模型异常简洁:权限永远授给角色,人通过成为角色的成员来获得权限。你不需要记住「用户表」「组表」「权限表」三套东西,只需要记住一条链路:对象 → 权限 → 角色 ← 成员 ← 人。
集群级实体:角色不属于某个数据库
角色定义在数据库集群(cluster)层面,而不是数据库层面。也就是说,CREATE ROLE 创建的角色在本实例的所有数据库中都有效,pg_authid 与 pg_auth_members 目录在整个集群里只有一份。
这一点有两层推论:
- 同名角色在不同数据库中是同一个角色,属性完全一致,不存在「A 库里 alice 是普通用户、B 库里是管理员」这种事。
- 权限(对象授权)才是数据库级的,角色能否连上某个库、能在这个库里做什么,由该库内的授权与 pg_hba.conf 共同决定。
角色属性一览
角色属性(role attributes)是「作用于角色本身」的特权,与对象权限是两套体系。CREATE ROLE 的全部属性如下:
| 属性 | 默认值 | 说明 |
|---|---|---|
SUPERUSER / NOSUPERUSER | NOSUPERUSER | 超级用户,绕过除登录外的一切权限检查 |
CREATEDB / NOCREATEDB | NOCREATEDB | 可否创建数据库 |
CREATEROLE / NOCREATEROLE | NOCREATEROLE | 可否创建、修改、删除其他角色 |
INHERIT / NOINHERIT | INHERIT | 成员权限是否自动继承可用(详见角色继承) |
LOGIN / NOLOGIN | NOLOGIN | 可否作为初始会话角色登录 |
REPLICATION / NOREPLICATION | NOREPLICATION | 可否建立复制连接、创建/删除复制槽 |
BYPASSRLS / NOBYPASSRLS | NOBYPASSRLS | 可否绕过全部行级安全策略 |
CONNECTION LIMIT connlimit | -1(不限) | 登录角色的并发连接数上限 |
[ ENCRYPTED ] PASSWORD '...' / PASSWORD NULL | NULL | 密码(永远以加密形式存储;无密码则密码认证必然失败) |
VALID UNTIL 'timestamp' | 无 | 密码失效时间(只管密码,不管角色本身) |
IN ROLE role_name | — | 新角色加入指定角色(成为其成员) |
ROLE role_name | — | 把指定角色加为新角色的成员 |
ADMIN role_name | — | 同 ROLE,但成员同时获得 ADMIN 选项 |
属性不是权限
SUPERUSER、CREATEDB、CREATEROLE 这类属性不能通过成员关系继承。即使 alice 是 admin 组的成员、而 admin 拥有 CREATEDB,alice 自己也建不了库——除非她 SET ROLE admin 切换身份。这是新手最常见的误解之一,后文与《角色继承》一页会反复强调。
语法与用法
CREATE ROLE
完整语法如下(PostgreSQL 18):
CREATE ROLE name [ [ WITH ] option [ ... ] ]
where option can be:
SUPERUSER | NOSUPERUSER
| CREATEDB | NOCREATEDB
| CREATEROLE | NOCREATEROLE
| INHERIT | NOINHERIT
| LOGIN | NOLOGIN
| REPLICATION | NOREPLICATION
| BYPASSRLS | NOBYPASSRLS
| CONNECTION LIMIT connlimit
| [ ENCRYPTED ] PASSWORD 'password' | PASSWORD NULL
| VALID UNTIL 'timestamp'
| IN ROLE role_name [, ...]
| ROLE role_name [, ...]
| ADMIN role_name [, ...]执行 CREATE ROLE 需要 CREATEROLE 特权或超级用户身份。几个最基础的例子:
-- 能登录、无密码(密码认证永远失败,适用于 peer/trust 等本地认证场景)
CREATE ROLE jonathan LOGIN;
-- 能登录、带密码
CREATE USER davide WITH PASSWORD 'jw8s0F4';
-- 带密码且密码有时效
CREATE ROLE miriam WITH LOGIN PASSWORD 'jw8s0F4' VALID UNTIL '2027-01-01';
-- 能建库、能管角色(常用于中级管理员)
CREATE ROLE admin WITH CREATEDB CREATEROLE;
-- 纯组角色(默认就是 NOLOGIN)
CREATE ROLE app_readonly;ALTER ROLE
ALTER ROLE 有三种形态:改属性、改名、设置会话默认参数。
-- 1. 修改属性(选项与 CREATE ROLE 相同)
ALTER ROLE davide WITH CREATEROLE CREATEDB;
ALTER ROLE davide NOSUPERUSER NOCREATEDB;
ALTER ROLE davide CONNECTION LIMIT 20;
ALTER ROLE davide PASSWORD 'new_secret';
ALTER ROLE davide VALID UNTIL '2026-12-31';
ALTER ROLE davide NOINHERIT;
-- 2. 重命名(不影响属主关系)
ALTER ROLE davide RENAME TO david;
-- 3. 设置该角色每次登录后的默认会话参数
ALTER ROLE app_user SET search_path = app, public;
ALTER ROLE app_user IN DATABASE appdb SET statement_timeout = '5s';
ALTER ROLE app_user RESET statement_timeout;改名有一个限制:如果角色是某个数据库对象的属主,或者在其他角色的成员关系中被引用,RENAME TO 在某些版本场景下会要求会话角色具备相应条件(最典型的报错场景是「会话用户与当前角色不一致」),通常以超级用户、且未 SET ROLE 的状态下执行即可。
DROP ROLE
DROP ROLE [ IF EXISTS ] name [, ...]DROP ROLE 会一并撤销该角色在各处的成员关系(它作为成员的身份),但不能删除仍然「拥有东西」的角色:只要该角色还拥有数据库对象、持有权限、或是组角色的成员,删除就会失败。完整流程见下文「DROP ROLE 与前置清理」。
SET ROLE / RESET ROLE
-- 临时切换为某个成员角色
SET ROLE app_readwrite;
-- 回到初始登录角色(两种写法等价)
RESET ROLE;
SET ROLE NONE;SET ROLE 是「使用组成员身份」的钥匙,与 INHERIT 语义的关系详见角色继承。
详细说明
LOGIN 与 NOLOGIN:能否登录的分水岭
LOGIN 是区分「用户」与「组」的唯一开关。不带 LOGIN 的角色不能出现在连接串的 user= 里,但依然可以拥有对象、接受授权、作为成员关系的节点。
-- 典型分工:组角色 + 登录角色
CREATE ROLE app_readwrite NOLOGIN; -- 组:读写
CREATE ROLE app_readonly NOLOGIN; -- 组:只读
CREATE USER alice; -- 人:登录角色
GRANT app_readwrite TO alice; -- alice 挂到读写组一般来说,人对应登录角色,职责对应组角色。人可以换(离职、入职),职责的授权却稳定不变——把权限直接授给一个个登录角色,很快就会演变成无法审计的权限泥潭。
SUPERUSER:绕过一切检查的危险属性
超级用户绕过数据库内除「登录认证」之外的所有权限检查:读写任意表、执行任意函数、访问任意系统目录。同时,只有超级用户能创建超级用户。
生产环境应当遵守一条铁律:应用连不上超级用户,人也尽量别用超级用户日常操作。超级用户只保留给实例运维(参数调整、恢复、升级),业务库的全部工作都应通过「角色 + 授权」完成。数据库里日常连接数中超级用户占比越高,权限体系就越形同虚设。
CREATEDB 与 CREATEROLE
这两个属性分别对应「造库」与「造角色」两类管理动作:
CREATEDB:可以CREATE DATABASE。注意它只管建库,不代表对新库里对象有任何权限——新库的属主是建库者本人。CREATEROLE:可以创建、修改(ALTER ROLE)、删除(DROP ROLE)角色,也可以对角色加注释、修改安全标签。在 PG16 之前,CREATEROLE的能力接近半个超级用户(可以把自己提权);PG16 起官方收紧了语义,详见下文「PG16 起 CREATEROLE 的收紧」。
INHERIT / NOINHERIT:继承的开关
INHERIT 决定「当本角色成为其他角色的成员时,那些角色的对象权限是否自动可用」。默认 INHERIT(自动可用);设为 NOINHERIT 后,成员权限必须 SET ROLE 切换过去才能使用。
这里只做铺垫,完整的语义(包括 PG16 引入的按成员关系控制的 inherit_option)请阅读角色继承(INHERIT / NOINHERIT)。
REPLICATION 与 BYPASSRLS
REPLICATION:允许以复制模式连接(物理或逻辑复制),并允许创建、删除复制槽。这是极高特权的属性,只应授予真正用于复制的角色(如repl)。只有超级用户或拥有REPLICATION的角色能授予它。BYPASSRLS:绕过表上的全部行级安全策略。行级安全(RLS)本身是一个大主题,在本站有专门章节;这里只需记住两点:默认NOBYPASSRLS,且超级用户与表属主(默认情况下)本来就不受 RLS 约束,所以BYPASSRLS主要服务于「普通业务角色但被信任可以看全量数据」的场景(例如pg_read_all_data的使用者想看全量数据时,官方建议就是给该角色设BYPASSRLS)。
CONNECTION LIMIT:连接数上限
CONNECTION LIMIT 为登录角色设置并发连接数上限,-1 表示不限。有三个容易忽略的细节:
- 对超级用户不生效:超级用户不受此限制(这也符合「留后门」的思路)。
- 只统计普通连接:prepared transactions 的连接与 background worker 不计入。
- 强制是近似的:两个会话恰好在只剩一个空位时同时发起,可能双双失败。
生产上,应用角色通常会配合连接池(如 PgBouncer)使用,此时 CONNECTION LIMIT 可以作为「池子打穿数据库」的兜底保险丝,例如限制为 max_connections 的 20%。
PASSWORD 与密码加密(SCRAM-SHA-256)
密码只对 LOGIN 角色有意义,但语法上允许给任何角色设置。关键规则:
- 密码永远以加密形式存储在系统目录中,
ENCRYPTED关键字可以接受但没有实际效果(向后兼容)。 - 加密方式由参数
password_encryption决定。PostgreSQL 14 起默认值为scram-sha-256;更早版本默认md5。 - 如果提交的密码串本身已经是 MD5 或 SCRAM 加密格式,则按原样存储(不再二次加密),这也是
pg_dumpall能原样迁移密码的原因。 PASSWORD NULL显式设置空密码:该角色的密码认证将永远失败(不等于免密登录)。- MD5 密码支持已被官方标记为弃用,未来版本将移除,新环境一律使用 SCRAM。
-- 查看当前密码加密方式
SHOW password_encryption;
-- 修改密码(SQL 方式:明文经网络传输,注意日志与历史记录)
ALTER ROLE app_user PASSWORD 'new_secret';
-- 更安全的方式:psql 的 \password 元命令(客户端加密后传输)
\password app_user明文密码的暴露面
在 psql / CREATE ROLE / ALTER ROLE 中直接写明文密码,可能进入 shell 历史、服务器日志(log_statement 开启时)、pg_stat_activity(旧版本)等位置。交互式场景请优先使用 \password;自动化场景请把密码放在环境变量或密钥管理系统里,而不是脚本常量。
VALID UNTIL:密码有效期
VALID UNTIL 只对密码认证生效:到期后密码认证失败,但角色本身仍然存在,通过证书、GSSAPI、peer 等非密码方式登录不受影响。常见用法:
-- 临时审计账号,90 天后自动失效
CREATE ROLE auditor LOGIN PASSWORD '...' VALID UNTIL '2026-12-31';
-- 取消有效期
ALTER ROLE auditor VALID UNTIL 'infinity';注意:VALID UNTIL 不会自动禁用或删除角色,它只是让「用户名 + 密码」这对凭证失效。
CREATE USER 与 CREATE GROUP
CREATE USER 是 CREATE ROLE ... LOGIN 的别名——唯一区别是隐含 LOGIN。CREATE GROUP 则等价于 CREATE ROLE ... NOLOGIN,属于更早期的历史写法。类似的还有 CREATE ROLE ... IN GROUP(与 IN ROLE 行为相同,PG18 文档已将其标记为弃用写法)以及 CREATE ROLE ... USER ...(同 ROLE 子句,同样弃用)。
-- 以下两两等价
CREATE USER alice; = CREATE ROLE alice LOGIN;
CREATE GROUP app_devs; = CREATE ROLE app_devs NOLOGIN;
CREATE ROLE alice IN ROLE app_devs;= CREATE ROLE alice IN GROUP app_devs;命令行工具 createuser 也封装了同样能力(内部就是调用 CREATE ROLE),并且以加密方式传输密码。
createuser:命令行工具
需要在 Shell 脚本、Ansible、容器初始化里建角色时,createuser 比 psql -c 更顺手:
# 交互式创建:逐项询问是否超级用户/可建库/可建角色等
createuser --interactive --pwprompt app_user
# 脚本化创建一个受限登录角色
createuser --login --connection-limit 10 reporter
# 创建超级用户(等价于 CREATE ROLE ... SUPERUSER LOGIN)
createuser --superuser admin_user
# --echo 显示它实际执行的 CREATE ROLE 语句,便于学习与审计
createuser --echo --login dev_user
# 指定目标实例与库(连接参数与 psql 一致)
createuser -h 127.0.0.1 -p 5432 -U postgres --login billing_svc两个与 psql 内写 SQL 的差异值得记住:其一,--pwprompt 让密码经终端输入并加密后传输,不会落入 shell 历史;其二,它默认连接到本地库执行,集群级角色意味着「连哪个库执行效果都一样」。
IN ROLE / ROLE / ADMIN:创建时顺手建立成员关系
三个子句解决「建角色时顺手挂进组」的需求,方向恰好相反:
| 子句 | 语义 | 默认选项 |
|---|---|---|
IN ROLE g | 新角色加入 g(新角色是成员) | SET 开、ADMIN 关、INHERIT 开(除非声明了 NOINHERIT) |
ROLE m | 把 m 加入新角色(新角色是组) | SET 开;m 拥有角色级 INHERIT 属性则 INHERIT 开 |
ADMIN m | 同 ROLE m,但成员获得 ADMIN 选项 | 成员可以继续把别人拉进组 |
CREATE ROLE app_readwrite NOLOGIN;
-- alice 创建后即为 app_readwrite 的成员
CREATE ROLE alice LOGIN IN ROLE app_readwrite;
-- 等价写法:先建后挂(更常用,GRANT 语法更通用)
CREATE ROLE alice LOGIN;
GRANT app_readwrite TO alice;官方文档的建议是:成员关系的增删优先用 GRANT / REVOKE,CREATE ROLE 的这些子句只适合「一次成型」的初始化脚本。
ALTER ROLE:改名、属性与配置参数
ALTER ROLE ... SET 是一个非常实用但常被忽略的能力:把会话参数固化为某角色的登录默认值。它有三层作用域,优先级从高到低:
ALTER ROLE ... IN DATABASE db SET ...——针对「某角色 + 某数据库」;ALTER ROLE ... SET ...——针对角色全局;postgresql.conf/ALTER SYSTEM——实例级默认。
-- 应用角色默认走 app 模式,并限制单语句超时
ALTER ROLE app_rw SET search_path = app, public;
ALTER ROLE app_rw IN DATABASE appdb SET statement_timeout = '30s';
-- 只读角色强制开启事务只读(双保险)
ALTER ROLE app_ro SET default_transaction_read_only = on;
-- DBA 角色默认打开自动解释执行计划
ALTER ROLE dba SET auto_explain.log_min_duration = '1s';角色 SET 与连接池
ALTER ROLE ... SET 在新连接建立时生效。经过连接池复用的旧连接不会立刻应用新值;生产上变更后需要对池执行对应的清空/重启操作(如 PgBouncer 的 RELOAD / PAUSE),或依靠池的 server_reset_query 让会话参数回落。
DROP ROLE 与前置清理
删除角色前必须清空它与世界的三条联系:它拥有的对象、它持有的权限、它作为成员的身份。相关工具有两个:
-- 把某角色的全部对象(含权限)转交给新角色
REASSIGN OWNED BY old_app TO app_owner;
-- 删除某角色在被授予方的全部对象与权限(含默认权限条目)
DROP OWNED BY old_app;
-- 然后才能删除角色本身
DROP ROLE old_app;两个命令都必须在目标角色拥有对象的每个数据库里执行。组角色的删除则相对简单:DROP ROLE 组角色时,其成员关系会被自动撤销,成员角色本身不受影响。详细流程将在《GRANT / REVOKE 权限管理》的「REASSIGN OWNED 与 DROP OWNED」小节展开。
PG16 起 CREATEROLE 的收紧
PG16 之前,CREATEROLE 的实际能力接近半个超级用户:持有者可以修改任意非超级用户角色,甚至可以把自己的密码改成某个已存在角色的等价凭证,从而横向提权。PG16 对此做了系统性收紧,方向可以概括为:
CREATEROLE角色不再能随意修改「任何」角色,而只能管理被授予了 ADMIN(管理)选项的角色;- 它创建角色时,会自动建立起「被创建角色 → 创建者」的、带
ADMIN/INHERIT/SET选项的成员关系(可用参数createrole_self_grant调整自动授予哪些选项); - 继承、
SET ROLE也从「角色级属性」细化为「每条成员关系各自的选项」(详见角色继承)。
版本差异要写进运维手册
同一份授权脚本在 PG15 与 PG16+ 上行为可能不同(尤其是依赖 CREATEROLE 自动化建角色的场景)。升级前请核对 role attributes 官方文档中关于 role creation 的说明,并在测试环境演练。
角色命名规范
角色名会出现在连接串、pg_stat_activity、日志、审计报表与 \du 输出里,命名混乱的代价会随时间放大。推荐的约定:
| 类型 | 模式 | 示例 |
|---|---|---|
| 人的登录角色 | 真实标识(工号/拼音/邮箱前缀) | zhangsan、li.siwang |
| 应用连接角色 | <应用>_<用途> | orders_svc、billing_api |
| 组角色 | <应用>_<职责> | app_readonly、app_readwrite、app_admin |
| 属主角色 | <应用>_owner | app_owner |
| 工具类角色 | <工具名> | flyway、pgbouncer_stats |
三条红线:
- 避开
pg_前缀:以pg_开头的名字为内置预设角色体系保留,日常命名不要触碰。 - 不给人用共享名:
dev、test、admin这类含糊的共享登录角色是审计噩梦,一人一号。 - 组名表职责不表人:
app_readwrite表达的是「读写这个职责」,而不是「张三李四的集合」——人员变动只动成员关系,组名与授权保持稳定。
命名定了之后,用注释(COMMENT ON ROLE)记录每个角色的用途与负责人,让 \du+ 的输出自带说明书。
内置预设角色
PostgreSQL 内置了一组预设角色(predefined roles),把常见的高频特权打包成可以直接 GRANT 的角色,避免管理员手工拼凑一堆细碎权限。它们不能被删除或改名,也不能作为成员关系的「被授予方」(不能 GRANT xxx TO pg_monitor)。
| 预设角色 | 能力说明 |
|---|---|
pg_database_owner | 隐式成员恒为「当前数据库的属主」;初始拥有 public 模式。把它当作「数据库属主占位符」用在授权里,模板库授权可随建库自动生效 |
pg_read_all_data | 以 SELECT 读取所有表、视图、序列,并拥有所有模式的 USAGE;不绕过 RLS |
pg_write_all_data | 对所有表、视图、序列拥有 INSERT / UPDATE / DELETE,并拥有所有模式的 USAGE;不绕过 RLS |
pg_read_all_settings | 读取全部配置参数(含仅超级用户可见的) |
pg_read_all_stats | 读取全部 pg_stat_* 统计视图与统计类扩展 |
pg_stat_scan_tables | 执行可能对表持有 ACCESS SHARE 锁较长时间的监控函数(如 pgrowlocks) |
pg_monitor | 监控合集:pg_read_all_settings + pg_read_all_stats + pg_stat_scan_tables 的成员 |
pg_signal_backend | 向其他会话发送取消查询 / 终止会话信号(不能作用于超级用户的会话) |
pg_signal_autovacuum_worker | 向 autovacuum worker 发送取消 / 终止信号(PG18 新增) |
pg_read_server_files | 通过 COPY 等文件函数读取服务器上数据库进程可访问的任意文件 |
pg_write_server_files | 通过 COPY 等文件函数写服务器文件 |
pg_execute_server_program | 以数据库进程用户身份执行服务器端程序 |
pg_checkpoint | 执行 CHECKPOINT 命令 |
pg_create_subscription | 在拥有数据库 CREATE 权限的前提下执行 CREATE SUBSCRIPTION(PG16 新增) |
pg_maintain | 对所有关系执行 VACUUM、ANALYZE、CLUSTER、REFRESH MATERIALIZED VIEW、REINDEX、LOCK TABLE(PG17 新增) |
pg_use_reserved_connections | 使用 reserved_connections 参数保留的连接槽(PG17 新增) |
三个「服务器级」角色约等于超级用户
pg_read_server_files、pg_write_server_files、pg_execute_server_program 允许绕过数据库级权限检查直接触达服务器文件系统与进程,完全可以被用来获取超级用户等价能力。除非明确知道自己在做什么,否则不要授予——尤其是不要授予给任何应用角色。
角色相关的系统视图与目录
角色信息分布在两个目录与三个视图中,按「能否看到密码」分层:
| 视图 / 目录 | 可见性 | 内容 |
|---|---|---|
pg_authid | 仅超级用户 | 全部角色的完整定义(集群级共享),含 rolpassword 密文 |
pg_shadow | 仅超级用户 | 仅 LOGIN 角色(历史命名),含真实密码密文 |
pg_auth_members | 公开可读 | 角色成员关系(roleid、member、grantor、admin_option 及 PG16 起的 inherit_option、set_option) |
pg_roles | 公开可读 | pg_authid 的脱敏视图:字段同名,rolpassword 恒为 ******** |
pg_user | 公开可读 | 仅 LOGIN 角色的脱敏信息(usename、usecreatedb、usesuper、userepl、usebypassrls、passwd、valuntil、useconfig) |
日常运维建议统一从 pg_roles 查询(能看全量、不泄露密码):
-- 登录角色一览
SELECT rolname, rolcanlogin, rolconnlimit, rolvaliduntil
FROM pg_roles
WHERE rolcanlogin
ORDER BY rolname;
-- 找出「危险角色」:带管理属性或绕过 RLS 的
SELECT rolname, rolsuper, rolcreatedb, rolcreaterole,
rolreplication, rolbypassrls
FROM pg_roles
WHERE rolsuper OR rolcreatedb OR rolcreaterole
OR rolreplication OR rolbypassrls;
-- 某角色挂在哪里(组成员视角)
SELECT r.rolname AS member, g.rolname AS "group",
am.admin_option, am.inherit_option, am.set_option
FROM pg_auth_members am
JOIN pg_roles r ON r.oid = am.member
JOIN pg_roles g ON g.oid = am.roleid
ORDER BY 1, 2;实战示例
示例一:创建应用角色
业务应用连接数据库应使用专用角色:能登录、有密码、限制连接数、固化 search_path,且不属于任何人类。
psql -U postgres -d appdb <<'SQL'
-- 应用读写角色(组,不登录)
CREATE ROLE app_rw NOLOGIN;
-- 应用连接角色(登录,挂到组)
CREATE ROLE app_conn LOGIN
PASSWORD 'Str0ng_P@ss'
CONNECTION LIMIT 30
NOINHERIT;
GRANT app_rw TO app_conn;
-- 连接默认行为固化
ALTER ROLE app_conn SET search_path = app, public;
ALTER ROLE app_conn IN DATABASE appdb SET statement_timeout = '10s';
-- 验证
SELECT rolname, rolcanlogin, rolconnlimit, rolconfig
FROM pg_roles WHERE rolname IN ('app_rw', 'app_conn');
SQL rolname | rolcanlogin | rolconnlimit | rolconfig
----------+-------------+--------------+--------------------------------------
app_conn | t | 30 | {search_path=app, public}
app_rw | f | -1 |
(2 rows)示例二:创建只读角色
只读角色是数据分析、报表类需求的标准答案。先建组角色,授权动作(GRANT 的完整细节在下一页)这里只需照抄:
psql -U postgres -d appdb <<'SQL'
CREATE ROLE app_readonly NOLOGIN;
-- 模式可见 + 未来新表自动可读(默认权限,详见后续页)
GRANT USAGE ON SCHEMA app TO app_readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA app TO app_readonly;
ALTER DEFAULT PRIVILEGES IN SCHEMA app GRANT SELECT ON TABLES TO app_readonly;
-- 挂成员
CREATE ROLE reporter LOGIN PASSWORD 'R3port_P@ss' VALID UNTIL '2026-12-31';
GRANT app_readonly TO reporter;
GRANT CONNECT ON DATABASE appdb TO reporter;
SQL示例三:创建监控角色
监控_exporter(postgres_exporter、pgwatch 等)只需要读统计视图与配置,绝不需要超级用户。pg_monitor 一个角色就够:
psql -U postgres -d postgres <<'SQL'
CREATE ROLE monitoring LOGIN PASSWORD 'M0nitor_P@ss' CONNECTION LIMIT 5;
GRANT pg_monitor TO monitoring;
-- 验证:能否读仅超级用户可见的配置
SET ROLE monitoring;
SHOW data_directory; -- pg_read_all_settings 使普通角色也能读
RESET ROLE;
SQL data_directory
---------------------------------------------
/var/lib/postgresql/18/main
(1 row)示例四:角色信息的日常巡检查询
-- 1. 哪些登录角色没有设置密码过期时间(合规检查项)
SELECT rolname
FROM pg_roles
WHERE rolcanlogin AND rolvaliduntil IS NULL AND rolname <> 'postgres';
-- 2. 哪些登录角色的连接数不受限
SELECT rolname, rolconnlimit
FROM pg_roles
WHERE rolcanlogin AND rolconnlimit = -1;
-- 3. 有成员的组角色及其成员数量
SELECT g.rolname AS group_role, count(*) AS members
FROM pg_auth_members am
JOIN pg_roles g ON g.oid = am.roleid
GROUP BY 1
ORDER BY members DESC;
-- 4. 危险信号:登录角色同时具备 CREATEROLE
SELECT rolname FROM pg_roles
WHERE rolcanlogin AND rolcreaterole;示例五:临时角色与生命周期管理
给第三方做临时数据核对的账号,应当「带期限、带限制、可追溯」:
psql -U postgres -d appdb <<'SQL'
-- 临时账号:30 天有效期 + 只读组成员
CREATE ROLE vendor_check LOGIN
PASSWORD 'V3ndor_Tmp'
VALID UNTIL now() + interval '30 days'
CONNECTION LIMIT 3;
GRANT app_readonly TO vendor_check;
COMMENT ON ROLE vendor_check IS '供应商临时核对账号,负责人:王五,工单:OPS-1024';
SQL到期后的处置要写成流程:账号密码认证自动失败只是第一步,正式下线仍要走「REVOKE 成员 → 逐库 DROP OWNED / REASSIGN OWNED → DROP ROLE」的完整三步。巡检时把 rolvaliduntil 已过期但仍存在的角色列出来,作为待清理清单:
SELECT rolname, rolvaliduntil
FROM pg_roles
WHERE rolcanlogin AND rolvaliduntil IS NOT NULL AND rolvaliduntil < now();注意事项
角色是集群级的,权限才是库级的。 在 A 库里 REVOKE 权限不会影响角色本身的存在,也不会影响它在 B 库的权限。做「租户隔离」时不要指望「不同数据库里的同名角色」,同名就是同一个。
DROP ROLE 的前置清理必须逐库执行。 REASSIGN OWNED / DROP OWNED 只作用于当前连接的数据库;角色如果在多个库里拥有对象,需要在每个库里跑一遍,最后才能删除角色。
不要用超级用户跑业务。 超级用户绕过一切检查,任何一次注入或误操作都是全库级事故;它还绕过 CONNECTION LIMIT、不受 RLS 约束。应用、报表、监控各用各的角色,超级用户只留给运维。
密码永远以密文存储,但「传输」可能明文。 CREATE ROLE ... PASSWORD 'xxx' 里的明文会经网络到达服务器,并可能落入 shell 历史与日志。交互式用 \password,脚本用环境变量或 .pgpass,并保证 password_encryption = scram-sha-256。
VALID UNTIL 只约束密码认证。 证书、GSSAPI、peer 认证不受它约束;不要把它当作「定时禁用账号」的完备方案,配合定期巡检 pg_roles.rolvaliduntil 使用。
MD5 已弃用。 老实例升级时检查 password_encryption 与 pg_authid.rolpassword 的前缀(MD5 开头即 MD5 密文),并安排滚动重置密码迁移到 SCRAM。
PG16 前后 CREATEROLE 行为不同。 依赖「CREATEROLE 可以管理所有角色」的自动化脚本在 PG16+ 上会开始报权限错误;应改为显式 GRANT ... WITH ADMIN OPTION。版本升级的变更清单里必须有这一条。
组角色命名要有约定。 推荐统一前缀(如 app_readonly / app_readwrite / app_admin),因为角色名在 \du、日志、pg_stat_activity 中都会出现,混乱的命名会让权限审计事倍功半。
每个角色都要有「户口」。 用 COMMENT ON ROLE 记录用途、负责人与关联工单;没有注释的角色半年后就是「没人敢删、没人说得清」的化石。
给角色级参数留迁移说明。 ALTER ROLE ... SET 的值藏在 pg_roles.rolconfig 里,连接串上看不见;应用侧排查「参数不对」时,记得这层配置的存在。
最佳实践
- 人、应用、职责分离成三类角色:人用
LOGIN角色登录;应用用专属LOGIN角色连接(一人一号,禁止共享);权限全部挂在NOLOGIN组角色上,通过成员关系分发。 - 登录角色一律显式写
LOGIN并给CONNECTION LIMIT:默认值NOLOGIN容易让人误以为「不写就是能登录」;连接数限制是连接风暴的保险丝。 - 密码策略写进标准:
password_encryption = scram-sha-256、关键角色VALID UNTIL有期限、交互改密用\password、连接串密码交给密钥管理而不是代码仓库。 - 超级用户只用于运维动作:建角色、改参数、恢复数据之外的日常操作(建表、调数、发布)都应由普通管理角色完成,并为其保留操作审计。
- 给中间管理角色
CREATEDB/CREATEROLE时最小化:能只给CREATEROLE就不要给超级用户;PG16+ 上它已被大幅收紧,是可以放心下放的最小管理特权。 - 善用
ALTER ROLE ... SET固化会话行为:search_path、statement_timeout、default_transaction_read_only按「角色 + 库」固化,比依赖客户端连接参数可靠得多。 - 监控与备份用预设角色:监控用
pg_monitor,备份读数据用pg_read_all_data(按需),不要为了「方便」把超级用户密码填进 exporter。 - 定期巡检危险属性:把「
rolsuper、rolcreaterole、rolbypassrls、rolreplication为真的角色清单」纳入巡检脚本,新增即告警。 - 成员关系变更走流程:
GRANT group TO user/REVOKE ... FROM是权限审计的核心事件,应在工单或 IaC 中留痕,避免口头授权。 - 删除角色用标准三步:
REASSIGN OWNED(逐库)→DROP OWNED(逐库)→DROP ROLE,并把三步写成一个脚本,防止漏库导致删除失败或半清理状态。 - 升级大版本前重读角色文档:PG16 收紧了
CREATEROLE与继承语义、PG17 新增了pg_maintain等预设角色,官方文档明确提示预设角色的权限可能随版本调整,升级前务必核对 release notes。
下一节
继续学习:GRANT / REVOKE 权限管理