Skip to content

postgres 用户与密码设置 ​

PostgreSQL 安装后会创建一个名为 postgres 的操作系统用户与同名数据库超级用户,它是集群的最高权限入口。本节将介绍 postgres 用户的双重身份、密码设置方法、业务用户创建规范与权限回收。

前置知识

阅读本节前,建议先了解:命令行美化(pgcli)

基础概念 ​

初学者最容易混淆的一点是:postgres 既是操作系统用户,也是数据库角色(Role)。两者名字相同,但处于不同层次:

层次名称作用
操作系统层postgres 系统用户运行 postgres 服务进程,拥有数据目录 PGDATA 的属主身份
数据库层postgres 数据库角色集群初始化时创建的超级用户(Superuser),可管理所有库与角色
数据库层app_user 业务角色应用实际使用的低权限账号,只拥有业务库的必要权限

数据库服务进程以系统用户 postgres 的身份运行,因此它对该用户拥有的文件天然可信——这正是初始 pg_hba.conf 中本地连接使用 peer 或 trust 认证的原因。postgres 角色则是用来做管理的,绝不应该给应用程序使用。

术语英文名说明
角色RolePostgreSQL 中用户与组的统一概念,可登录的角色即「用户」
超级用户Superuser绕过所有权限检查的角色,postgres 默认是超级用户
对等认证peer依据操作系统用户名判断数据库身份的本地认证方式
标识认证ident通过 ident 服务查询远端操作系统用户的认证方式
密码文件.pgpass客户端保存密码的文件,需 chmod 600
权限Privilege对对象(表、序列、schema)的操作许可
所有权Ownership对象的属主,隐含全部权限

语法与用法 ​

创建角色与设置密码 ​

sql
-- 创建可登录角色(LOGIN 才能连接)
CREATE ROLE app_user LOGIN PASSWORD 'Strong_Pass_2026';

-- 创建时直接授予部分属性
CREATE ROLE report_user LOGIN
  PASSWORD 'Another_Strong_2026'
  CONNECTION LIMIT 20
  VALID UNTIL '2027-01-01';

-- 修改已有角色密码
ALTER ROLE app_user PASSWORD 'New_Strong_Pass_2026';

-- 让密码永不过期(默认即为 infinity,显式写出更清晰)
ALTER ROLE app_user VALID UNTIL 'infinity';

-- 删除角色(需先转移或删除其拥有的对象)
DROP ROLE IF EXISTS temp_user;
sql
-- 设置 postgres 超级用户密码(在集群本机以 postgres 系统用户执行)
\password postgres
-- 或使用 SQL(密码会进入日志与历史,不推荐)
ALTER ROLE postgres PASSWORD 'Pg_Super_Strong_2026';
bash
# 以 postgres 系统用户进入 psql(本地 peer 认证,无需密码)
sudo -u postgres psql

# 或显式指定角色
sudo -u postgres psql -d postgres -U postgres

# macOS(Homebrew 安装):默认当前用户即超级用户
psql -d postgres

角色属性一览 ​

属性语法说明
LOGINCREATE ROLE x LOGIN允许连接数据库
SUPERUSER / NOSUPERUSERALTER ROLE x NOSUPERUSER是否绕过权限检查
CREATEDB / NOCREATEDBALTER ROLE x CREATEDB是否可创建数据库
CREATEROLE / NOCREATEROLEALTER ROLE x NOCREATEROLE是否可创建/修改角色
INHERIT / NOINHERITALTER ROLE x INHERIT是否自动继承所属角色的权限
REPLICATIONALTER ROLE x REPLICATION是否可发起物理复制连接
BYPASSRLSALTER ROLE x BYPASSRLS是否绕过行级安全策略
CONNECTION LIMITALTER ROLE x CONNECTION LIMIT 20最大并发连接数,-1 为不限
VALID UNTILALTER ROLE x VALID UNTIL '...'账号到期时间
PASSWORDALTER ROLE x PASSWORD '...'设置密码,NULL 表示清除

PostgreSQL 14 起,CREATEDB、CREATEROLE 等权限不再隐含在 SUPERUSER 之外的其他属性中;CREATEROLE 也不再自动获得对其他角色的管理权。给业务角色授权时不要顺手给 CREATEROLE。

授权与回收 ​

sql
-- 库级:允许连接与创建对象
GRANT CONNECT ON DATABASE demo TO app_user;
GRANT USAGE, CREATE ON SCHEMA public TO app_user;

-- 表级:读写业务表
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO app_user;

-- 序列级:使用自增序列(比表权限更容易被遗忘)
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO app_user;

-- 让后续新建对象自动带上权限(对当前角色创建的对象生效)
ALTER DEFAULT PRIVILEGES IN SCHEMA public
  GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO app_user;
ALTER DEFAULT PRIVILEGES IN SCHEMA public
  GRANT USAGE, SELECT ON SEQUENCES TO app_user;

-- 只读账号:仅授予查询能力
GRANT CONNECT ON DATABASE demo TO report_user;
GRANT USAGE ON SCHEMA public TO report_user;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO report_user;
sql
-- 回收权限:默认是 RESTRICT,有依赖时会报错,可加 CASCADE
REVOKE CREATE ON SCHEMA public FROM app_user;

-- 回收所有表的写权限(打造只读角色)
REVOKE INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public FROM app_user;

-- 查看某角色的全部权限
\dp public.users
\ddp
sql
-- PostgreSQL 15 起 public schema 默认不再允许所有人 CREATE
-- 显式确认当前状态(新集群应为 OWNER 为 pg_database_owner)
SELECT nspname, nspowner::regrole AS owner, nspacl
FROM pg_namespace
WHERE nspname = 'public';

-- 若仍是旧行为,显式收紧
REVOKE CREATE ON SCHEMA public FROM PUBLIC;

详细说明 ​

认证方式如何决定「要不要密码」 ​

postgres 密码是否有用,取决于 pg_hba.conf 中该连接匹配到的认证方法:

认证方法是否需要密码典型场景
trust不需要,任何人可连仅限隔离的本地开发环境
peer不需要,凭系统用户身份Linux 上 sudo -u postgres psql
ident不需要,凭 ident 服务早期内网部署
md5需要(MD5 摘要)兼容老客户端
scram-sha-256需要(SCRAM,默认推荐)生产环境唯一正确选择
cert需要客户端证书双向 TLS 场景
reject直接拒绝黑名单

这意味着:如果你用 peer 认证连本地,改 postgres 密码不会影响你的登录;而远程连接用 scram-sha-256 时,密码就是唯一的门槛。

sql
-- 查看密码加密方式(password_encryption 决定新建密码的存储算法)
SHOW password_encryption;
-- scram-sha-256

-- 查看角色的密码有效期与是否已设置密码
SELECT rolname, rolcanlogin, rolsuper, rolvaliduntil,
       (rolpassword IS NOT NULL) AS has_password,
       rolpassword LIKE 'SCRAM-SHA-256%' AS is_scram
FROM pg_authid
WHERE rolname IN ('postgres', 'app_user');
bash
# 配置密码存储算法(postgresql.conf),改后需重载
# password_encryption = scram-sha-256
sudo systemctl reload postgresql

密码管理的三条铁律 ​

  1. 密码只走 scram-sha-256:md5 已被证明不适合作为长期方案,且旧客户端可能无法协商。设置 password_encryption = scram-sha-256 后,ALTER ROLE ... PASSWORD 才会以 SCRAM 格式落库。
  2. 应用永不使用超级用户:postgres 角色能 DROP DATABASE、能读所有表、能创建新角色。应用被注入或配置泄露时,损失是全局的。
  3. 密码不进代码与命令行:命令行参数会进入 shell 历史与 ps 输出;代码里的明文会进 Git。改用 PGPASSWORD、~/.pgpass 或密钥管理服务。
bash
# 正确:用 .pgpass,权限必须是 600,否则 libpq 会直接忽略该文件
cat > ~/.pgpass <<'EOF'
127.0.0.1:5432:demo:app_user:Strong_Pass_2026
db.example.com:5432:demo:app_user:Strong_Pass_2026
EOF
chmod 600 ~/.pgpass

# 验证:不再需要交互输入密码
psql -h 127.0.0.1 -U app_user -d demo -c "SELECT current_user;"
bash
# 错误!密码出现在命令行与历史中,且可能被 ps 捕获
# 不推荐
psql "postgresql://app_user:Strong_Pass_2026@127.0.0.1:5432/demo"

# 错误!把密码写进代码仓库
# 不推荐
echo 'DB_PASSWORD=Strong_Pass_2026' >> app/.env && git add app/.env

业务角色的最小权限模型 ​

一个可落地的权限模型通常包含三类角色:

角色权限用途
app_owner拥有 schema 与表,可 DDL迁移脚本执行者(CI/CD 专用)
app_user表的 DML(增删改查)+ 序列 USAGE应用运行时账号
report_user仅 SELECT报表、BI、排查问题
sql
-- 完整落地示例:在 demo 库中建立三层角色
-- 1) 迁移专用账号(拥有对象,可执行 DDL)
CREATE ROLE app_owner LOGIN PASSWORD 'Owner_Pass_2026';
CREATE SCHEMA IF NOT EXISTS app AUTHORIZATION app_owner;

-- 2) 应用运行账号(只做 DML)
CREATE ROLE app_user LOGIN PASSWORD 'App_Pass_2026' CONNECTION LIMIT 100;

-- 3) 只读账号
CREATE ROLE report_user LOGIN PASSWORD 'Report_Pass_2026';

-- 授权:让 app_owner 建的对象默认给 app_user 权限
ALTER DEFAULT PRIVILEGES FOR ROLE app_owner IN SCHEMA app
  GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO app_user;
ALTER DEFAULT PRIVILEGES FOR ROLE app_owner IN SCHEMA app
  GRANT USAGE, SELECT ON SEQUENCES TO app_user;
ALTER DEFAULT PRIVILEGES FOR ROLE app_owner IN SCHEMA app
  GRANT SELECT ON TABLES TO report_user;

GRANT USAGE ON SCHEMA app TO app_user, report_user;

排查「连不上」的通用顺序 ​

现象首要怀疑验证方式
password authentication failed密码错 / pg_hba.conf 方法不匹配查看服务端日志中的详细行
no pg_hba.conf entry for hostpg_hba.conf 没有覆盖该来源SELECT * FROM pg_hba_file_rules;
role "xxx" does not exist角色未创建 / 连到了另一个集群\du
permission denied for table xxx缺对象权限\dp xxx
too many connections超出连接上限见下文查询
FATAL: remaining connection slots...连接数耗尽检查应用连接池
sql
-- 查看当前连接使用情况与各角色连接数
SELECT usename, count(*) AS conns, state
FROM pg_stat_activity
GROUP BY usename, state
ORDER BY conns DESC;

-- 查看角色级连接上限与全局上限
SELECT rolname, rolconnlimit FROM pg_roles WHERE rolcanlogin;
SHOW max_connections;

实战示例 ​

示例一:把默认的「裸」集群改造为可上线的账号体系 ​

bash
# 第 1 步:用 peer 认证进入,创建业务角色(此时还没有任何密码风险)
sudo -u postgres psql -d postgres
sql
-- 第 2 步:为 postgres 设置强密码(后续改 pg_hba 后可远程管理)
\password postgres

-- 第 3 步:创建业务库与角色
CREATE DATABASE demo OWNER app_owner;
CREATE ROLE app_owner LOGIN PASSWORD 'Owner_Pass_2026';
CREATE ROLE app_user  LOGIN PASSWORD 'App_Pass_2026'  CONNECTION LIMIT 100;
CREATE ROLE report_user LOGIN PASSWORD 'Report_Pass_2026';

-- 第 4 步:在 demo 库中授权
\c demo
GRANT CONNECT ON DATABASE demo TO app_user, report_user;
GRANT USAGE ON SCHEMA public TO app_user, report_user;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO app_user;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO app_user;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO report_user;
GRANT SELECT, USAGE ON ALL SEQUENCES IN SCHEMA public TO report_user;
bash
# 第 5 步:用业务账号验证「能连、但权限受限」
psql "host=127.0.0.1 dbname=demo user=app_user" -c "\conninfo"

# 验证超级用户操作被拒绝(应当报错,这才是正确的)
psql "host=127.0.0.1 dbname=demo user=app_user" -c "CREATE DATABASE hack;"
# ERROR:  permission denied to create database

示例二:审计并清理多余的超级用户 ​

sql
-- 列出所有超级用户(正常应只有 postgres 与极少数运维账号)
SELECT rolname, rolcanlogin, rolcreatedb, rolcreaterole, rolvaliduntil
FROM pg_roles
WHERE rolsuper
ORDER BY rolname;

-- 列出长期未使用的可登录角色(需先启用连接统计)
SELECT r.rolname,
       coalesce(s.last_use, '从未使用') AS last_use
FROM pg_roles AS r
LEFT JOIN (
  SELECT usename, max(state_change)::date AS last_use
  FROM pg_stat_activity
  GROUP BY usename
) AS s ON s.usename = r.rolname
WHERE r.rolcanlogin
ORDER BY s.last_use NULLS LAST;
sql
-- 降权:把误授的超级用户收回(保留 LOGIN,去掉一切特权)
ALTER ROLE legacy_admin NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION;

-- 直接禁用账号(保留角色以便审计,但不能登录)
ALTER ROLE departed_user NOLOGIN;

-- 硬删除(先处理其拥有的对象)
REASSIGN OWNED BY departed_user TO app_owner;
DROP OWNED BY departed_user;
DROP ROLE departed_user;
bash
# 修改后强制重载配置(若涉及 pg_hba.conf 或角色认证缓存)
sudo systemctl reload postgresql

# 用重建连接的方式验证(旧连接仍持有旧权限,必须重连)
psql -h 127.0.0.1 -U app_user -d demo -c "SELECT current_user, session_user;"

注意事项 ​

  • postgres 角色不要用于应用:无论多小的服务,都应为它建独立账号。共享超级用户会让「谁改的数据」无法追溯。
  • 密码不会即时失效:ALTER ROLE ... PASSWORD 不会踢掉已建立的连接。改密后需主动断开旧连接(见下方 SQL)才能让新密码生效于下次认证。
  • role 与 user 的差别:CREATE USER 等价于 CREATE ROLE ... LOGIN。日常用 CREATE ROLE 更明确。
  • 对象所有权决定删除权:DROP ROLE 前必须处理其拥有的对象,否则报错。REASSIGN OWNED + DROP OWNED 是标准清理组合。
  • public schema 的历史包袱:PostgreSQL 15 之前 PUBLIC 拥有 CREATE 权限,这是很多「为什么普通用户能建表」的根因。新集群已修正,但升级上来的库仍可能残留旧权限。
  • .pgpass 权限严格:权限不是 600 时 libpq 会静默忽略,表现为「配置了但还要输密码」。Windows 上该文件名为 %APPDATA%\postgresql\pgpass.conf。
  • 别在 SQL 里写明文密码:ALTER ROLE ... PASSWORD 'xxx' 会记录到服务端日志(log_statement=all 时)与 psql 历史。交互场景优先用 \password。
sql
-- 改密后断开该角色的旧连接(谨慎:会中断业务,需配合重连)
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE usename = 'app_user'
  AND pid <> pg_backend_pid();
  • 连接上限是全局资源:给角色设 CONNECTION LIMIT 只能防单个角色抢占,真正的保护是应用侧连接池(如 PgBouncer)与 max_connections 的合理配置。

最佳实践 ​

  • 三层账号模型:app_owner(DDL,仅 CI 使用)、app_user(DML,应用使用)、report_user(只读)。三者密码独立、互不复用。
  • 密码策略:长度 ≥ 20 位、由密钥管理服务生成与轮换;VALID UNTIL 为运维账号设到期时间,业务账号用 infinity 但走定期轮换。
  • 强制 SCRAM:password_encryption = scram-sha-256,并在 pg_hba.conf 中只允许 scram-sha-256(或 cert)。
  • 默认权限一步到位:用 ALTER DEFAULT PRIVILEGES 保证新建表自动继承权限,避免「上线后发现新表没权限」。
  • 权限也纳入版本控制:把角色与授权语句写进迁移脚本,与表结构一起评审、一起回滚。
  • 定期审计:把「超级用户清单」「无密码角色」「长期不登录角色」「超出连接上限」做成巡检 SQL,纳入日常检查。
sql
-- 巡检模板:一次跑完四项账号安全检查
-- 1) 超级用户清单
SELECT 'superusers' AS check_item, string_agg(rolname, ', ') AS detail
FROM pg_roles WHERE rolsuper;
-- 2) 可登录但无密码的角色
SELECT 'no_password' AS check_item, string_agg(rolname, ', ') AS detail
FROM pg_authid WHERE rolcanlogin AND rolpassword IS NULL;
-- 3) 密码非 SCRAM 的角色
SELECT 'weak_password_algo' AS check_item, string_agg(rolname, ', ') AS detail
FROM pg_authid WHERE rolpassword IS NOT NULL AND rolpassword NOT LIKE 'SCRAM-SHA-256%';
-- 4) 当前连接数占用
SELECT 'connections' AS check_item,
       count(*)::text || ' / ' || current_setting('max_connections') AS detail
FROM pg_stat_activity;

下一节

继续学习:pg_hba.conf 访问控制

参考链接 ​