PostgreSQL

关注公众号 jb51net

关闭
首页 > 数据库 > PostgreSQL > PostgreSQL修改管理员密码

PostgreSQL数据库修改管理员密码的方法有哪些

作者:liuziyi

如果你忘记了PostgreSQL密码怎么办,别慌,下面小编就教大家如何利用pg_hba.conf的trust规则和peer认证,在无需重启数据库的情况下安全修改密码吧

版本兼容矩阵

版本关键差异
PG 9.6 及更早password_encryption 为布尔值 on/off,只支持 MD5,无 SCRAM
PG 10 ~ 13password_encryption 为枚举值,默认 md5,可切 scram-sha-256
PG 14 ~ 17password_encryption 默认 scram-sha-256
PG 18同上,md5 仍可显式指定但已不推荐
PG 9.4+pg_hba.conf 支持 include / include_if_exists / include_dir

脚本

#!/usr/bin/env bash
# ============================================================
# PostgreSQL 管理员密码修改工具
# 用法:bash pg_chpass.sh --help
# ============================================================
set -uo pipefail
SCRIPT_NAME=$(basename "$0")
SCRIPT_VERSION="1.0.0"
usage(){
cat <<EOF
${SCRIPT_NAME} ${SCRIPT_VERSION} — PostgreSQL 管理员密码修改工具
用法:
  bash ${SCRIPT_NAME} [选项]
选项:
  -h, --help                显示本帮助并退出(不连接数据库)
  -V, --version             显示脚本版本并退出
  -u, --user <角色名>       要改密的角色,默认 postgres
  -p, --password <新密码>   新密码;不提供则交互输入(推荐)
      --old-password <旧密码>
                            已知旧密码时直接认证,避免修改 pg_hba.conf
  -H, --host <主机>         连接主机,默认 127.0.0.1
  -P, --port <端口>         连接端口,默认 5432
  -d, --database <库名>     连接库,默认 postgres
  -D, --datadir <数据目录>  指定 PGDATA;不指定则自动探测
      --hba <文件>          指定 pg_hba.conf;不指定则自动探测
      --no-hba-edit         禁止修改 pg_hba.conf,只用 peer 或旧密码认证
      --no-verify           跳过改密后的登录验证
      --dry-run             只打印将要执行的动作,不实际执行
      --                    结束选项解析
环境变量:
  PG_NEW_PASS               等价于 --password
  PG_OLD_PASS               等价于 --old-password
  PGPORT / PGDATA           未通过选项指定时使用
认证路径(按顺序自动尝试):
  1. 本地 peer 认证:以操作系统用户 postgres 直连,不需要密码
  2. 旧密码认证:使用 --old-password 通过 TCP 连接
  3. trust 兜底:临时在 pg_hba.conf 顶部插入 trust 规则并 reload
版本兼容:
  PG 9.6 及更早   password_encryption 为布尔值,仅支持 MD5
  PG 10 ~ 13      枚举值,默认 md5,可切 scram-sha-256
  PG 14 ~ 18      默认 scram-sha-256
  脚本会读取 pg_hba.conf 中实际使用的认证方法,自动对齐加密算法
执行前提:
  1. 建议以 root 或 postgres 用户执行
  2. 改密本身不需要重启,只做 reload,业务不中断
  3. trust 窗口期仅对本机开放,脚本结束会自动还原 pg_hba.conf
  4. 流复制环境中修改复制用户密码后,需同步更新备库 primary_conninfo
退出码:
  0  成功
  1  执行失败
  2  参数错误
示例:
  bash ${SCRIPT_NAME} --help
  bash ${SCRIPT_NAME} -u postgres
  bash ${SCRIPT_NAME} -u postgres -p 'N3wP@ssw0rd'
  bash ${SCRIPT_NAME} -u postgres --old-password 'oldpass'
  bash ${SCRIPT_NAME} -u postgres --no-hba-edit
  bash ${SCRIPT_NAME} --dry-run
安全提示:
  使用 --password 或位置参数时,密码会出现在 ps 与 shell 历史中;
  生产环境建议交互输入,或改用 PG_NEW_PASS 环境变量。
EOF
}
err(){  echo "[错误] $*" >&2; }
info(){ echo "[信息] $*"; }
warn(){ echo "[警告] $*" >&2; }
# ---------- 参数解析 ----------
DB_USER="postgres"
NEW_PASS="${PG_NEW_PASS:-}"
OLD_PASS="${PG_OLD_PASS:-}"
PGHOST="${PGHOST:-127.0.0.1}"
PGPORT="${PGPORT:-5432}"
PGDATABASE="postgres"
DATADIR="${PGDATA:-}"
HBA_FILE=""
EDIT_HBA=1
DO_VERIFY=1
DRY_RUN=0
while [ $# -gt 0 ]; do
  case "$1" in
    -h|--help)    usage; exit 0 ;;
    -V|--version) echo "${SCRIPT_NAME} ${SCRIPT_VERSION}"; exit 0 ;;
    -u|--user)        [ $# -ge 2 ] || { err "$1 需要参数"; exit 2; }; DB_USER="$2"; shift 2 ;;
    --user=*)         DB_USER="${1#*=}"; shift ;;
    -p|--password)    [ $# -ge 2 ] || { err "$1 需要参数"; exit 2; }; NEW_PASS="$2"; shift 2 ;;
    --password=*)     NEW_PASS="${1#*=}"; shift ;;
    --old-password)   [ $# -ge 2 ] || { err "$1 需要参数"; exit 2; }; OLD_PASS="$2"; shift 2 ;;
    --old-password=*) OLD_PASS="${1#*=}"; shift ;;
    -H|--host)        [ $# -ge 2 ] || { err "$1 需要参数"; exit 2; }; PGHOST="$2"; shift 2 ;;
    --host=*)         PGHOST="${1#*=}"; shift ;;
    -P|--port)        [ $# -ge 2 ] || { err "$1 需要参数"; exit 2; }; PGPORT="$2"; shift 2 ;;
    --port=*)         PGPORT="${1#*=}"; shift ;;
    -d|--database)    [ $# -ge 2 ] || { err "$1 需要参数"; exit 2; }; PGDATABASE="$2"; shift 2 ;;
    --database=*)     PGDATABASE="${1#*=}"; shift ;;
    -D|--datadir)     [ $# -ge 2 ] || { err "$1 需要参数"; exit 2; }; DATADIR="$2"; shift 2 ;;
    --datadir=*)      DATADIR="${1#*=}"; shift ;;
    --hba)            [ $# -ge 2 ] || { err "$1 需要参数"; exit 2; }; HBA_FILE="$2"; shift 2 ;;
    --hba=*)          HBA_FILE="${1#*=}"; shift ;;
    --no-hba-edit)    EDIT_HBA=0; shift ;;
    --no-verify)      DO_VERIFY=0; shift ;;
    --dry-run)        DRY_RUN=1; shift ;;
    --)               shift; break ;;
    -*)               err "未知选项:$1"; echo "使用 ${SCRIPT_NAME} --help 查看帮助" >&2; exit 2 ;;
    *)                err "多余的参数:$1"; echo "使用 ${SCRIPT_NAME} --help 查看帮助" >&2; exit 2 ;;
  esac
done
[ $# -gt 0 ] && { err "多余的参数:$*"; exit 2; }
# ---------- 密码输入 ----------
if [ -z "$NEW_PASS" ]; then
  printf "请输入角色 %s 的新密码:" "$DB_USER"
  stty -echo 2>/dev/null
  read -r NEW_PASS
  stty echo 2>/dev/null
  echo
fi
[ -z "$NEW_PASS" ] && { err "新密码不能为空"; exit 2; }
case "$DB_USER" in
  *'"'*|*"'"*|*" "*) err "角色名不合法"; exit 2 ;;
esac
# SQL 单引号转义
NEW_PASS_SQL=$(printf '%s' "$NEW_PASS" | sed "s/'/''/g")
# ---------- 定位可执行文件 ----------
PSQL=$(command -v psql 2>/dev/null || true)
if [ -z "$PSQL" ]; then
  PSQL=$(ls -1 /usr/pgsql-*/bin/psql /usr/lib/postgresql/*/bin/psql \
              /usr/local/pgsql/bin/psql 2>/dev/null | sort -V | tail -1)
fi
[ -n "$PSQL" ] && [ -x "$PSQL" ] || { err "未找到 psql,请确认 PostgreSQL 已安装"; exit 1; }
PG_CTL=$(dirname "$PSQL")/pg_ctl
[ -x "$PG_CTL" ] || PG_CTL=$(command -v pg_ctl 2>/dev/null || true)
# ---------- 判定执行身份 ----------
AS_POSTGRES=0
if [ "$(id -u)" -eq 0 ] && id postgres >/dev/null 2>&1; then
  AS_POSTGRES=1
fi
pg_exec_sql(){
  local sql="$1"; shift
  local cmd="$PSQL -X -q -t -A -v ON_ERROR_STOP=1 $* -f -"
  if [ "$AS_POSTGRES" -eq 1 ]; then
    printf '%s\n' "$sql" | su - postgres -s /bin/bash -c "$cmd"
  else
    printf '%s\n' "$sql" | bash -c "$cmd"
  fi
}
psql_direct(){
  local sql="$1"; shift
  printf '%s\n' "$sql" | "$PSQL" -X -q -t -A -v ON_ERROR_STOP=1 "$@" -f -
}
# ---------- 探测数据目录 ----------
detect_datadir(){
  local d
  d=$(ps -eo args= 2>/dev/null | grep -E '[p]ostgres' \
      | sed -n 's/.*-D[[:space:]]*\([^[:space:]]*\).*/\1/p' | head -1)
  [ -n "$d" ] && [ -f "$d/PG_VERSION" ] && { echo "$d"; return 0; }
  for d in /var/lib/pgsql/data /var/lib/pgsql/*/data \
           /var/lib/postgresql/*/main /usr/local/pgsql/data \
           /data/pgdata /data/postgres; do
    [ -f "$d/PG_VERSION" ] && { echo "$d"; return 0; }
  done
  return 1
}
if [ -z "$DATADIR" ]; then
  DATADIR=$(detect_datadir) || true
fi
# ---------- 重载配置 ----------
reload_pg(){
  if [ -n "$PG_CTL" ] && [ -n "$DATADIR" ] && [ -d "$DATADIR" ]; then
    if [ "$AS_POSTGRES" -eq 1 ]; then
      su - postgres -s /bin/bash -c \
        "'$PG_CTL' reload -D '$DATADIR'" >/dev/null 2>&1 && return 0
    else
      "$PG_CTL" reload -D "$DATADIR" >/dev/null 2>&1 && return 0
    fi
  fi
  if command -v systemctl >/dev/null 2>&1; then
    systemctl reload postgresql >/dev/null 2>&1 && return 0
    systemctl reload postgresql-* >/dev/null 2>&1 && return 0
  fi
  return 1
}
# ---------- 认证路径 1:peer ----------
CONN_MODE=""
try_peer(){
  local out
  out=$(pg_exec_sql "SELECT 'PEER_OK';" -d "$PGDATABASE" 2>/dev/null) || return 1
  case "$out" in *PEER_OK*) CONN_MODE="peer"; return 0 ;; esac
  return 1
}
# ---------- 认证路径 2:旧密码 ----------
try_oldpw(){
  [ -z "$OLD_PASS" ] && return 1
  local out
  out=$(PGPASSWORD="$OLD_PASS" psql_direct "SELECT 'OLD_OK';" \
        -h "$PGHOST" -p "$PGPORT" -U "$DB_USER" -d "$PGDATABASE" 2>/dev/null) || return 1
  case "$out" in *OLD_OK*) CONN_MODE="pw"; return 0 ;; esac
  return 1
}
exec_val(){
  local sql="$1"
  case "$CONN_MODE" in
    peer) pg_exec_sql "$sql" -d "$PGDATABASE" ;;
    *)    psql_direct "$sql" -h "$PGHOST" -p "$PGPORT" -U "$DB_USER" -d "$PGDATABASE" ;;
  esac
}
# ---------- 主流程 ----------
info "角色:$DB_USER   连接:$PGHOST:$PGPORT/$PGDATABASE"
if [ "$DRY_RUN" -eq 1 ]; then
  echo "----- 将执行(dry-run,未实际执行)-----"
  echo "ALTER USER \"$DB_USER\" WITH PASSWORD '******';"
  [ "$EDIT_HBA" -eq 1 ] && echo "必要时临时修改 pg_hba.conf 并 reload(不改动 restart)"
  echo "---------------------------------------"
  exit 0
fi
if try_peer; then
  info "认证方式:本地 peer 认证(无需密码)"
elif try_oldpw; then
  info "认证方式:旧密码认证"
else
  if [ "$EDIT_HBA" -eq 0 ]; then
    err "peer 与旧密码认证均失败,且已禁用 pg_hba.conf 修改"
    exit 1
  fi
  warn "常规认证失败,准备临时启用 trust 认证"
  if [ -z "$DATADIR" ] || [ ! -d "$DATADIR" ]; then
    err "未定位到数据目录,请用 -D 指定 PGDATA"
    exit 1
  fi
  [ -n "$HBA_FILE" ] || HBA_FILE="$DATADIR/pg_hba.conf"
  [ -f "$HBA_FILE" ] || { err "未找到 pg_hba.conf:$HBA_FILE"; exit 1; }
  HBA_BAK="${HBA_FILE}.bak.$(date +%Y%m%d%H%M%S)"
  cp -a "$HBA_FILE" "$HBA_BAK" || { err "备份 pg_hba.conf 失败"; exit 1; }
  info "pg_hba.conf 已备份:$HBA_BAK"
  RESTORED=0
  restore_hba(){
    [ "$RESTORED" -eq 1 ] && return 0
    RESTORED=1
    info "还原 pg_hba.conf..."
    cp -a "$HBA_BAK" "$HBA_FILE"
    reload_pg && info "已 reload" || warn "reload 失败,请手动执行 pg_ctl reload"
  }
  trap 'restore_hba; exit 1' INT TERM
  {
    echo "# ==== temporary password reset $(date '+%F %T') ===="
    echo "local   all             all                                     trust"
    echo "host    all             all             127.0.0.1/32            trust"
    echo "host    all             all             ::1/128                 trust"
    cat "$HBA_BAK"
  } > "$HBA_FILE"
  chown --reference="$HBA_BAK" "$HBA_FILE" 2>/dev/null || true
  chmod --reference="$HBA_BAK" "$HBA_FILE" 2>/dev/null || true
  if ! reload_pg; then
    err "reload 失败,请检查数据目录权限"
    restore_hba
    exit 1
  fi
  sleep 2
  CONN_MODE="trust"
  if ! psql_direct "SELECT 'TRUST_OK';" -h "$PGHOST" -p "$PGPORT" \
       -U "$DB_USER" -d "$PGDATABASE" >/dev/null 2>&1; then
    err "trust 模式下仍无法连接,请检查 listen_addresses 与端口"
    restore_hba
    exit 1
  fi
  info "认证方式:临时 trust"
fi
# ---------- 读取版本与配置 ----------
VER_NUM=$(exec_val "SHOW server_version_num;" 2>/dev/null | tr -dc '0-9')
VER=$(exec_val "SHOW server_version;" 2>/dev/null | tr -d '[:space:]')
[ -n "$VER" ] && info "版本:$VER"
if [ -z "$HBA_FILE" ]; then
  HBA_FILE=$(exec_val "SHOW hba_file;" 2>/dev/null | tr -d '[:space:]')
fi
# ---------- 对齐加密算法 ----------
ENC_SQL=""
if [ -n "$VER_NUM" ] && [ "$VER_NUM" -ge 100000 ]; then
  AUTH_METHOD=""
  if [ -n "$HBA_FILE" ] && [ -f "$HBA_FILE" ]; then
    if grep -qiE '^[[:space:]]*(host|hostssl|hostnossl)[^#]*scram-sha-256' "$HBA_FILE"; then
      AUTH_METHOD="scram-sha-256"
    elif grep -qiE '^[[:space:]]*(host|hostssl|hostnossl)[^#]*md5' "$HBA_FILE"; then
      AUTH_METHOD="md5"
    fi
  fi
  if [ -z "$AUTH_METHOD" ]; then
    if [ "$VER_NUM" -ge 140000 ]; then AUTH_METHOD="scram-sha-256"; else AUTH_METHOD="md5"; fi
  fi
  ENC_SQL="SET password_encryption = '$AUTH_METHOD';"
  info "加密算法:$AUTH_METHOD(依据 pg_hba.conf 与版本自动对齐)"
fi
# ---------- 执行改密 ----------
ALTER_SQL="${ENC_SQL}
ALTER USER \"$DB_USER\" WITH PASSWORD '$NEW_PASS_SQL';
SELECT 'ALTER_OK';"
OUT=$(exec_val "$ALTER_SQL" 2>&1)
RC=$?
printf '%s\n' "$OUT" | grep -v '^ALTER_OK$' | grep -v '^SET$' || true
if [ "$RC" -ne 0 ] || ! printf '%s\n' "$OUT" | grep -q 'ALTER_OK'; then
  err "改密失败,请根据上面的错误定位"
  case "$OUT" in
    *"does not exist"*)        warn "角色不存在,请用 -u 指定正确的角色名" ;;
    *"must be superuser"*)     warn "当前连接角色不是超级用户,需用 postgres 或具备 CREATEROLE 权限的角色" ;;
    *"password authentication failed"*) warn "旧密码错误" ;;
  esac
  exit 1
fi
info "ALTER USER 执行成功"
# ---------- 还原 pg_hba.conf ----------
if [ "${RESTORED:-0}" -eq 0 ] && [ -n "${HBA_BAK:-}" ]; then
  restore_hba
  trap - INT TERM
fi
# ---------- 验证 ----------
if [ "$DO_VERIFY" -eq 1 ]; then
  if [ -n "$HBA_FILE" ] && [ -f "$HBA_FILE" ] && \
     grep -qE '^[[:space:]]*host[^#]*127\.0\.0\.1' "$HBA_FILE"; then
    info "验证新密码登录..."
    if PGPASSWORD="$NEW_PASS" "$PSQL" -X -q -t -A -h "$PGHOST" -p "$PGPORT" \
         -U "$DB_USER" -d "$PGDATABASE" -c "SELECT 'VERIFY_OK';" 2>/dev/null \
         | grep -q 'VERIFY_OK'; then
      info "验证通过:新密码可以正常登录"
    else
      warn "新密码验证失败。若 pg_hba.conf 使用 md5 而密码以 scram 存储(或反之),请检查 password_encryption 设置"
      exit 1
    fi
  else
    warn "pg_hba.conf 中未找到 127.0.0.1 的 host 条目,跳过 TCP 验证"
    warn "可手动执行:psql -h $PGHOST -p $PGPORT -U $DB_USER -d $PGDATABASE"
  fi
fi
info "完成"
exit 0

使用示例

bash pg_chpass.sh --help
bash pg_chpass.sh -u postgres
bash pg_chpass.sh -u postgres -p 'N3wP@ssw0rd'
bash pg_chpass.sh -u postgres --old-password 'oldpass'
bash pg_chpass.sh -u postgres --no-hba-edit
bash pg_chpass.sh --dry-run

关键点说明

项目说明
不需要重启PG 改密和 pg_hba.conf 变更都只需 reload(pg_ctl reload 或 SELECT pg_reload_conf()),业务连接不受影响
peer 认证优先Linux 默认 local all all peer,以 postgres 系统用户登录完全不需要密码,这是最干净的路径,脚本首选
trust 规则必须放顶部pg_hba.conf 是自上而下首条匹配生效,临时规则必须插到文件开头,追加到末尾会被原有规则抢先匹配而无效
只开放本机临时规则仅限 local、127.0.0.1/32、::1/128,不对外网开放,窗口期极短
算法自动对齐脚本读取 pg_hba.conf 中实际的 md5 / scram-sha-256,用 SET password_encryption 在会话级对齐,避免改完登不上
会话级设置使用 SET password_encryption 而非 ALTER SYSTEM,不改全局配置,无需额外 reload
角色名与密码转义角色名用双引号包裹,密码中的单引号自动转义为 ''
文件属主还原用 chown --reference / chmod --reference 保持 pg_hba.conf 原有属主与权限,避免 postgres 无法读取
中断保护trap 确保 Ctrl+C 时也还原 pg_hba.conf 并 reload

高风险提示

  1. 流复制环境:修改复制用户(通常为 replicator)密码后,备库的 primary_conninfo 中保存的旧密码会导致复制中断,必须同步更新并 reload 备库。若使用 pgpass 文件,也要同步修改。
  2. pg_hba.conf 使用 include_dir:PG 9.4+ 支持拆分配置,临时规则插在主文件顶部通常仍能生效(主文件内容优先),但若主文件本身只有 include 行,需确认插入位置确实在 include 之前。
  3. listen_addresses 未包含 127.0.0.1:此时 trust 兜底路径会连不上,脚本会报错并自动还原,不会留下不安全状态。
  4. 连接池与中间件:改密后 PgBouncer、应用连接池、监控采集、备份脚本中的旧密码都要同步更新,否则会出现批量认证失败。
  5. PG 9.6 及更早:password_encryption 是布尔值,脚本会自动跳过 SET password_encryption 语句,按 MD5 处理。
  6. 国产兼容分支:openGauss、GaussDB、KingbaseES 等虽沿用 pg_hba.conf 与 ALTER USER 语法,但在角色体系、密码策略函数、三权分立模式上存在差异,本脚本不保证直接可用,需按目标产品核对。

到此这篇关于PostgreSQL数据库修改管理员密码的方法有哪些的文章就介绍到这了,更多相关PostgreSQL修改管理员密码内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文