受支持版本: 当前版本 (18) / 16 / 15 / 14
开发版本: devel

32.20. OAuth 支持 #

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

当启用支持并安装该可选模块后,如果服务器在认证期间请求 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添加自定义流程的更多信息,请参见Section 32.20.1

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

Note

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

32.20.1. Authdata 钩子 #

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

PQsetAuthDataHook #

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

void PQsetAuthDataHook(PQauthDataHook_type hook);

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

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

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

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

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

PQgetAuthDataHook #

获取当前的PGauthDataHook值。

PQauthDataHook_type PQgetAuthDataHook(void);

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

32.20.1.1. 钩子类型 #

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

PQAUTHDATA_PROMPT_OAUTH_DEVICE #

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

typedef struct _PGpromptOAuthDevice
{
    const char *verification_uri;   /* verification URI to visit */
    const char *user_code;          /* user code to enter */
    const char *verification_uri_complete;  /* optional combination of URI and
                                             * code, or NULL */
    int         expires_in;         /* seconds until user code expires */
} PGpromptOAuthDevice;

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

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

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

PQAUTHDATA_OAUTH_BEARER_TOKEN #

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

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

typedef struct PGoauthBearerRequest
{
    /* Hook inputs (constant across all calls) */
    const char *openid_configuration; /* OIDC discovery URL */
    const char *scope;                /* required scope(s), or NULL */

    /* Hook outputs */

    /*
     * Callback implementing a custom asynchronous OAuth flow. The signature is
     * platform-dependent: PQ_SOCKTYPE is SOCKET on Windows, and int everywhere
     * else.
     */
    PostgresPollingStatusType (*async) (PGconn *conn,
                                        struct PGoauthBearerRequest *request,
                                        PQ_SOCKTYPE *altsock);

    /* Callback to clean up custom allocations. */
    void        (*cleanup) (PGconn *conn, struct PGoauthBearerRequest *request);

    char       *token;   /* acquired Bearer token */
    void       *user;    /* hook-defined allocated data */
} PGoauthBearerRequest;

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

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

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

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

32.20.2. 调试与开发者设置 #

通过设置环境变量PGOAUTHDEBUG=UNSAFE,可以启用一种危险的调试模式。该功能仅用于便于本地开发和测试。它会做一些你绝不会希望生产系统去做的事情:

  • 允许在与 OAuth 提供者交互期间使用未加密的 HTTP

  • 允许使用环境变量PGOAUTHCAFILE完全替换系统受信任的 CA 列表

  • 在 OAuth 流程期间把 HTTP 流量(其中包含多个关键机密)打印到标准错误

  • 允许使用零秒重试间隔,这可能导致客户端忙等并无谓地消耗 CPU

Warning

不要把 OAuth 流量的输出分享给第三方。它包含可被用来攻击你的客户端和服务器的机密。



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

提交更正

如果您发现文档中有不正确的内容、与您使用特定功能的经验不符或需要进一步说明,请使用此表单来报告文档问题。