DEBUG.WIKI
京东云;让 AI 进入真实业务;零售 · 物流 · 客服 · 硬件;查看方案 →
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国
Evoxt;最高 6.0GHz CPU;高频云服务器 · 免费每周备份;查看配置 →

psql 连接时报 invalid percent-encoded token

psql 连接 URI 报 invalid percent-encoded token,可能是密码中的百分号未正确编码。区分 shell 引号与 URI 编码,使用密码提示或密码文件保留原始密码,并核对实际连接目标。

把带特殊字符的密码直接放进 PostgreSQL 连接 URL 后,psql 可能在连接前报错:

psql: error: invalid percent-encoded token: "demo%pass"

上面的密码是虚构示例。% 在 URI 中表示百分号编码的开头,后面应当跟两个十六进制数字。%pa 不符合这个格式,libpq 在解析连接串时就会拒绝它。给整条 URL 加引号只能处理 shell 的解释问题,不能让里面的 % 自动变成合法编码。

本文针对 psql 使用的 libpq 连接 URI。其他数据库驱动、应用配置格式或连接池可能有自己的解析规则,不应把同一条转义后的字符串到处复制。

先把密码从 URL 中拿出来验证

如果只是手动连接数据库,不必把密码嵌入 URI。保留原连接的主机、端口、数据库、用户名及 TLS 参数,让 psql 单独提示输入密码,可以避开密码字段的 URI 编码。

下面假设使用 TCP 连接,示例值都要替换成实际配置:

psql -h db.example.invalid -p 5432 -U app_user -d appdb -W

在密码提示中输入原始密码,例如实际密码里有 % 就输入 %,不要输入 %25。-W 强制在连接前提示密码;它不是在命令后面接密码值的选项。

如果原连接要求特定 TLS 参数,可以保留不含密码的连接 URI,再加 -W。例如下面的配置要求校验证书和主机名,证书路径也只是占位示例:

psql 'postgresql://app_user@db.example.invalid:5432/appdb?sslmode=verify-full&sslrootcert=/path/to/root.crt' -W

不要为了验证编码问题顺便删掉原来要求的 TLS 校验。若用户名也包含特殊字符,例如 @,上面的 URI 仍需正确编码用户名;使用分开的 -U 参数则直接传原始用户名,并按所用 shell 的规则引用它。

这个检查能区分两个阶段:若不再报 invalid percent-encoded token,说明这条连接参数的解析已经走过原来的错误位置;若接下来报认证失败、证书错误或连接超时,那是新的检查方向。只有成功建立预期连接,才能继续做数据库操作。

必须使用 URI 时,只编码字段值

连接 URI 的结构可以写成:

postgresql://用户名:密码@主机:端口/数据库

其中 ://、用户名与密码之间的 :、主机前的 @ 等分隔符要保留。需要编码的是字段值里会干扰 URI 解析的字符,而不是把整个连接串一次性当作普通文本编码。

常见字符的编码如下:

原始字符 放入 URI 字段中的编码
% %25
@ %40
/ %2F
? %3F
# %23
空格 %20

例如,虚构密码 demo%pa/ss@word 放进 URI 的密码字段时,应写成 demo%25pa%2Fss%40word。下面只是连接串结构示意,不要把真实密码按这种形式贴进公开日志:

postgresql://app_user:demo%25pa%2Fss%40word@db.example.invalid:5432/appdb

密码中的 % 即使后面恰好是合法十六进制数字,也要表达清楚原意。例如,原始密码字面上含有 %41,如果在 URI 中原样写 %41,解析后得到的是 A;要保留字面上的三个字符,应写成 %2541。

编码前先确认输入是原始密码还是已经编码过的密码。把已经编码的 %25 再编码一遍,会得到 %2525,连接时还原出的值就不同了。不要一边手工替换、一边又让配置工具重复编码。

用户名、密码等字段应分别处理。若输入还要经过 Compose、shell、CI 变量或应用模板,核对最终传给客户端的值,但不要把含凭据的完整值输出到共享日志。URI 编码是可逆的格式转换,不会隐藏密码。

自动任务可以使用密码文件

对于定时任务,libpq 的密码文件能把密码与连接 URI 分开保存。在 Unix 系统上,默认文件是运行用户家目录下的 .pgpass,也可以通过 PGPASSFILE 指定文件。Windows 默认位置是 %APPDATA%\postgresql\pgpass.conf。

文件每一行的格式为:

hostname:port:database:username:password

下面继续使用前面的虚构密码。密码文件不是 URI,因此 %、/、@ 在这里保持原样:

db.example.invalid:5432:appdb:app_user:demo%pa/ss@word

若字段里包含冒号或反斜杠,则要按密码文件规则分别写成 \: 或 \\。不要在这里套用 URI 百分号编码,也不要把 shell 的引号习惯搬进文件给密码额外加引号。

通过安全编辑方式写入实际凭据后,Unix 文件权限需要禁止组用户和其他用户访问,例如:

chmod 0600 /path/to/app.pgpass

路径换成实际文件,并保证任务运行用户能读取它。libpq 在 Unix 上会忽略权限过宽的密码文件。不要通过扩大文件权限来解决读取问题,应确认文件归属和运行身份。

指定该文件后,使用不含密码的连接参数:

PGPASSFILE=/path/to/app.pgpass psql -h db.example.invalid -p 5432 -U app_user -d appdb

这段示例没有覆盖原部署的 TLS 设置;需要显式参数的任务,应沿用之前核对过的不含密码 URI 或其他既有连接配置。不要同时在 URI 中留下旧密码,否则单独准备的密码文件可能不会被使用。

密码文件采用第一条匹配记录,前四个字段可以使用 *。排查时优先写准确的主机、端口、数据库和用户名,避免一条宽泛记录抢先匹配。文件由哪个用户读取,也取决于实际任务身份,终端中的登录用户与服务用户未必相同。

连接成功后核对目标,再恢复原任务

进入 psql 后,可以查看当前连接:

\conninfo

确认数据库、用户、主机和端口都是预期值,再恢复原先的业务操作。仅仅不再出现解析错误,不能证明密码正确,更不能证明后续备份或写入任务已经成功。

这类修正通常只需要改连接参数的表示方式,不需要重置数据库密码或调整认证规则。变更前保留原配置的安全备份;若切换到密码文件后无法连接,先检查匹配字段、文件权限和执行用户。需要回退时恢复原来的连接配置,并妥善处理新增的凭据文件,避免在项目目录或版本库中留下副本。