↑↓ 选择↵ 打开⌫ 切换范围完整搜索

PG.CENTER 连接 PostgreSQL 文档、百科与生态知识。由 Pigsty 维护。

支持中的版本: 当前版本 (18)
开发中的版本: 19 / 20devel
开发快照。 PostgreSQL 20devel 尚未正式发布,内容仍可能变化。

32.19. OAuth 支持 #

libpq 通过一个可选模块实现了对 OAuth v2 设备授权客户端流程的支持,该流程记录在 RFC 8628 中。关于如何启用内置的设备授权流程,请参见安装文档。

当启用支持并安装该可选模块后,如果服务器在认证期间请求 Bearer 令牌,libpq 默认会使用内置流程。即使运行客户端应用的系统没有可用的 Web 浏览器,例如通过 SSH 运行客户端时,也可以使用这一流程。

内置流程默认会打印一个需要访问的 URL 以及一个要在该处输入的用户代码:

$ psql 'dbname=postgres oauth_issuer=https://example.com oauth_client_id=...'
Visit https://example.com/device and enter the code: ABCD-EFGH

(这一提示可以被定制。)然后,用户将在其 OAuth 提供者处登录,提供者会询问是否允许 libpq 和服务器代表其执行操作。在继续之前,始终应当仔细检查显示的 URL 和权限,确认它们符合预期。不要向不受信任的第三方授予权限。

客户端应用可以实现自己的流程,以定制交互方式以及与应用程序的集成。关于如何向 libpq 添加自定义流程的更多信息,请参见第 32.19.1 节。

要使某个 OAuth 客户端流程可用,连接字符串至少必须包含 oauth_issuer 和 oauth_client_id。(这些设置由你所在组织的 OAuth 提供者决定。)此外,内置流程还要求 OAuth 授权服务器发布一个设备授权端点。

注意

当前 Windows 上尚不支持内置的设备授权流程。不过,仍然可以实现自定义客户端流程。

32.19.1. Authdata 钩子 #

客户端可以通过下面的钩子 API 修改或替换 OAuth 流程的行为:

PQsetAuthDataHook #

设置 PGauthDataHook,以覆盖 libpq 对其 OAuth 客户端流程一个或多个方面的处理。

void PQsetAuthDataHook(PQauthDataHook_type hook);

如果 hook 是 NULL,则会重新安装默认处理器。否则,应用程序需要传入一个回调函数指针,其签名如下:

int hook_fn(PGauthData type, PGconn *conn, void *data);

当需要应用程序执行某个操作时,libpq 会调用该回调。type 描述所提出的请求,conn 是正在认证的连接句柄,而 data 指向该请求特有的元数据。这个指针所指内容由 type 决定;支持的列表见第 32.19.1.1 节。

钩子可以串联起来,以支持协作和/或后备行为。一般来说,钩子实现应检查传入的 type(以及可能的请求元数据和/或当前 conn 的设置),以决定是否处理某一项 authdata。如果不处理,则应把请求委托给链中的前一个钩子(可通过 PQgetAuthDataHook 获取)。

返回一个大于零的整数表示成功。返回负整数表示发生错误并放弃此次连接尝试。(值零保留给默认实现。)

PQgetAuthDataHook #

获取当前的 PGauthDataHook 值。

PQauthDataHook_type PQgetAuthDataHook(void);

在初始化阶段(即首次调用 PQsetAuthDataHook 之前),该函数会返回 PQdefaultAuthDataHook。

32.19.1.1. 钩子类型 #

下面定义了这些 PGauthData 类型及其对应的 data 结构体:

PQAUTHDATA_PROMPT_OAUTH_DEVICE #

适用于 PostgreSQL 18 及更高版本。

在内置的设备授权客户端流程期间,替换默认的用户提示。data 指向一个 PGpromptOAuthDevice 实例:

typedef struct _PGpromptOAuthDevice
{
    const char *verification_uri;   /* 要访问的验证 URI */
    const char *user_code;          /* 要输入的用户代码 */
    const char *verification_uri_complete;  /* 可选的 URI 与代码组合,
                                             * 或为 NULL */
    int         expires_in;         /* 距用户代码过期的秒数 */
} PGpromptOAuthDevice;

可被包含在 libpq 中的 OAuth 设备授权流程要求最终用户使用浏览器访问一个 URL,然后输入一个代码,以允许 libpq 代表其连接到服务器。默认提示只是把 verification_uri 和 user_code 打印到标准错误。替换实现可以按任意偏好的方式展示这些信息,例如使用 GUI。

该回调只会在内置设备授权流程中被调用。如果应用程序安装了自定义 OAuth 流程,或者 libpq 构建时未启用内置流程支持,则不会使用这种 authdata 类型。

如果提供了不为 NULL 的 verification_uri_complete,则可以选择把它用于非文本形式的验证(例如显示二维码)。在这种情况下,仍应向最终用户显示 URL 和用户代码,因为该代码需要由提供者手工确认,而 URL 也使得用户在无法使用非文本方式时仍可继续。更多信息见 RFC 8628 第 3.3.1 节。

PQAUTHDATA_OAUTH_BEARER_TOKEN #

适用于 PostgreSQL 18 及更高版本。

添加一个自定义流程实现;如果内置流程已安装,则用它替换内置流程。该钩子应当要么直接返回一个适用于当前用户、签发者及授权范围组合的 Bearer 令牌(如果能在不阻塞的情况下获得),要么设置一个异步回调来获取令牌。

注意

对于 PostgreSQL 19 及更高版本,应用程序应优先使用 PQAUTHDATA_OAUTH_BEARER_TOKEN_V2。

data 指向一个 PGoauthBearerRequest 实例,应该由实现填充:

typedef struct PGoauthBearerRequest
{
    /* 钩子输入(在所有调用中保持不变) */
    const char *openid_configuration; /* OIDC 发现 URL */
    const char *scope;                /* 所需的授权范围,或为 NULL */

    /* 钩子输出 */

    /*
     * 实现自定义异步 OAuth 流程的回调。签名与平台有关:
     * 在 Windows 上,PQ_SOCKTYPE 是 SOCKET;在其他平台上则为 int。
     */
    PostgresPollingStatusType (*async) (PGconn *conn,
                                        struct PGoauthBearerRequest *request,
                                        PQ_SOCKTYPE *altsock);

    /* 清理自定义分配内存的回调。 */
    void        (*cleanup) (PGconn *conn, struct PGoauthBearerRequest *request);

    char       *token;   /* 获取的 Bearer 令牌 */
    void       *user;    /* 钩子定义并分配的数据 */
} PGoauthBearerRequest;

libpq 会向该钩子提供两项信息:openid_configuration 包含描述授权服务器所支持流程的 OAuth 发现文档 URL,而 scope 包含访问服务器所需的 OAuth 授权范围列表(以空格分隔,可以为空)。两者中的任意一个或两个都可能为 NULL,表示无法发现该信息。(在这种情况下,实现可以通过其他预先配置的知识来确定要求,或者选择失败。)

该钩子的最终输出是 token,它必须指向一个可在该连接上使用的有效 Bearer 令牌。(该令牌应由 oauth_issuer 所指定的签发者签发,并持有所请求的授权范围,否则连接会被服务器的验证器模块拒绝。)分配得到的令牌字符串必须在 libpq 完成连接之前始终有效;该钩子应设置 cleanup 回调,以便在 libpq 不再需要该令牌时调用。

如果实现在首次调用钩子时无法立即生成 token,应设置 async 回调,以处理与授权服务器之间的非阻塞通信。[16] 从钩子返回后,会立即调用该回调以启动流程。当回调无法在不阻塞的情况下继续时,应先将 *altsock 设为一个文件描述符,该描述符会在可以继续时被标记为可读或可写,然后返回 PGRES_POLLING_READING 或 PGRES_POLLING_WRITING。(该描述符随后会通过 PQsocket() 提供给顶层轮询循环。)流程完成时,应先设置 token,再返回 PGRES_POLLING_OK;返回 PGRES_POLLING_FAILED 表示失败。

实现可能希望在多次调用 async 和 cleanup 回调之间保存额外的数据用于记录状态。为此提供了 user 指针;libpq 不会触碰其内容,应用程序可以按自己的需要使用它。(记得在令牌清理时释放相关分配。)

PQAUTHDATA_OAUTH_BEARER_TOKEN_V2 #

适用于 PostgreSQL 19 及更高版本。

提供 PQAUTHDATA_OAUTH_BEARER_TOKEN 的全部功能,并且还允许设置自定义错误消息以及获取客户端已信任的 OAuth 签发者 ID。

data 指向一个 PGoauthBearerRequestV2 实例:

typedef struct
{
    PGoauthBearerRequest v1;    /* 参见上面的 PGoauthBearerRequest 结构体 */

    /* 钩子输入(在所有调用中保持不变) */
    const char *issuer;            /* 使用中的签发者标识符(RFC 9207) */

    /* 钩子输出 */
    const char *error;             /* 钩子定义的错误消息 */
} PGoauthBearerRequestV2;

应用程序必须首先使用 v1 结构体成员来实现基础 API,如上面的说明所述。libpq 还保证,传给 v1.async 和 v1.cleanup 回调的 request 指针可以安全地强制转换为 (PGoauthBearerRequestV2 *),以便使用下面描述的附加成员。

警告

只有当钩子类型为 PQAUTHDATA_OAUTH_BEARER_TOKEN_V2 时,才可以安全地强制转换为 (PGoauthBearerRequestV2 *)。如果钩子实现试图在处理 v1(PQAUTHDATA_OAUTH_BEARER_TOKEN)请求时访问 v2 成员,应用程序可能会崩溃或表现异常。

除版本 1 API 的功能外,v2 结构体还为该钩子提供了一个额外输入和一个额外输出:

issuer 包含当前连接所使用的签发者标识符(issuer identifier),定义见 RFC 9207。该标识符来自 oauth_issuer。为避免混淆攻击,自定义流程应确保授权服务器提供的任何发现元数据都与该签发者 ID 一致。

当流程失败时,error 可以被设置为指向一条自定义错误消息。该消息会作为 PQerrorMessage 的一部分包含进去。钩子必须在 v1.cleanup 回调期间释放为错误消息分配的所有内存。

32.19.2. 调试与开发者设置 #

在针对本地授权服务器开发时,可以考虑在客户端应用中使用 oauth_ca_file 连接参数(或者等效的 PGOAUTHCAFILE 环境变量)。

可以通过设置 PGOAUTHDEBUG 环境变量启用调试功能。该功能旨在方便本地开发和测试。该变量接受一个以逗号分隔的调试选项列表:

PGOAUTHDEBUG=option1,option2,...         仅安全选项
PGOAUTHDEBUG=UNSAFE:option1,option2,...  使用危险选项时
PGOAUTHDEBUG=UNSAFE                      旧格式;启用所有选项

可用的调试选项:

http(危险)

允许在与 OAuth 提供者交互期间使用未加密的 HTTP。这会让 OAuth 凭据通过未加密连接传输,极其危险,只应在本地测试时使用。

trace(危险)

在 OAuth 流程期间把 HTTP 流量打印到标准错误。这个输出包含 Bearer 令牌、客户端密钥、访问令牌和授权码等关键机密。不要把它分享给第三方。

dos-endpoint(危险)

允许使用零秒重试间隔,而不是正常的至少一秒。这样可以加快测试,但在正常运行时会导致客户端忙等,消耗 CPU 和网络资源。

call-count(安全)

当 OAuth 流程完成时,把对流程插件的调用总次数打印到标准错误。这有助于开发者调试异步回调行为。

plugin-errors(安全)

把插件加载错误打印到标准错误。这有助于开发者和包维护者调试 OAuth 插件加载失败的问题。

危险选项(http、trace、dos-endpoint)需要 UNSAFE:前缀。如果在没有该前缀的情况下指定了危险选项,或者某个选项名无法识别,则会把警告打印到标准错误并忽略该选项。列表中的其他有效选项仍然会继续生效。安全选项(call-count、plugin-errors)可以不带前缀使用。

示例:

PGOAUTHDEBUG=call-count              仅安全选项
PGOAUTHDEBUG=UNSAFE:http,trace       启用 HTTP 和流量日志
PGOAUTHDEBUG=UNSAFE:http,call-count  危险选项与安全选项混用

警告

永远不要在生产环境中使用危险的调试选项。它们会暴露可被用来攻击客户端和服务器的机密与行为。不要把 trace 输出分享给第三方。



[16] 在 PQAUTHDATA_OAUTH_BEARER_TOKEN 钩子回调中执行阻塞操作,会干扰诸如 PQconnectPoll 之类的非阻塞连接 API,并阻止并发连接继续推进。那些只使用同步连接原语(例如 PQconnectdb)的应用程序,可以在钩子中同步获取令牌,而不是实现 async 回调,但这样它们必然一次只能处理一个连接。

报告文档问题

阅读 上游文档. 反馈更正前请先核对 当前版本手册.