libpq 作为一个可选模块, 实现了对 OAuth v2 设备授权客户端流程的支持, 该流程记录于 RFC 8628。 有关如何启用把设备授权作为内建流程的支持, 请参见 安装文档。
当支持被启用且可选模块已安装时, 如果服务器在身份验证期间 请求一个 bearer 令牌, libpq 默认会使用内建流程。 即使运行客户端应用程序的系统没有可用的网页浏览器 (例如通过 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_issuer 和 oauth_client_id。 (这些设置由你的组织的 OAuth 提供方决定。) 内建流程还要求 OAuth 授权服务器发布一个设备授权端点。
Windows 目前不支持内建的设备授权流程。 仍然可以实现自定义的客户端流程。
客户端可以使用以下钩子 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
决定;
受支持的列表请参见
Section 32.20.1.1。
钩子可以被串联起来,
以允许协作和/或后备行为。
一般而言,
钩子实现应当检查传入的
type
(以及可能的请求元数据和/或正在使用的特定
conn
的设置),
来决定是否处理某一块 authdata。
如果不处理,
它应当委托给链中的上一个钩子
(可通过
PQgetAuthDataHook
检索)。
通过返回一个大于零的整数来指示成功。 返回一个负整数表示一种错误情况, 并放弃连接尝试。 (零值被保留给默认实现。)
PQgetAuthDataHook #
检索
PGauthDataHook
的当前值。
PQauthDataHook_type PQgetAuthDataHook(void);
在初始化时
(在第一次调用
PQsetAuthDataHook
之前),
这个函数会返回
PQdefaultAuthDataHook。
以下
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
设备授权流程要求最终用户用浏览器访问一个 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
#添加一个流程的自定义实现, 如果该流程 已安装, 则替换内建的流程。 如果当前用户/颁发者/作用域组合的 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
作用域的一个
(可能为空的)
以空格分隔的列表。
其中任何一个或两者都可以是
NULL,
表示无法发现该信息。
(在这种情况下,
实现也许能够使用某些其他预先配置的知识来确定这些要求,
或者它们可能选择失败。)
钩子的最终输出是
token,
它必须指向一个有效的 Bearer
令牌供连接使用。
(这个令牌应当由
oauth_issuer
颁发,
并且持有所需的作用域,
否则连接将被服务器的验证器模块拒绝。)
被分配的令牌字符串必须在
libpq
完成连接之前保持有效;
钩子应当设置一个
cleanup
回调,
当
libpq
不再需要它时该回调会被调用。
如果一个实现无法在钩子的初始调用期间立即产生一个
token,
它应当设置
async
回调来处理与授权服务器的非阻塞通信。
[11]
这个回调会在从钩子返回之后被立即调用,
以开始该流程。
当回调在不阻塞的情况下无法取得进一步进展时,
它应当在把
*altsock
设置为将来可以再次取得进展时会被标记为可读/可写的文件描述符
之后,
返回
PGRES_POLLING_READING 或
PGRES_POLLING_WRITING。
(这个描述符随后会通过
PQsocket()
提供给顶层轮询循环。)
当流程完成时,
在设置好
token
之后返回
PGRES_POLLING_OK;
若要指示失败,
则返回
PGRES_POLLING_FAILED。
实现可能希望在多次调用
async 和
cleanup
回调之间存储一些额外的数据用于簿记。
为此提供了
user
指针;
libpq
不会触碰它的内容,
应用程序可以在方便时使用它。
(记住在令牌清理期间释放任何分配。)
可以通过设置环境变量
PGOAUTHDEBUG=UNSAFE
来启用一种
“危险的调试模式”。
提供这个功能只是为了便于本地开发和测试。
它会做一些你不希望生产系统去做的事情:
允许在 OAuth 提供方交换期间使用未加密的 HTTP
允许用
PGOAUTHCAFILE
环境变量完全替换系统受信任的 CA 列表
在 OAuth 流程期间把 HTTP 流量 (包含若干关键秘密) 打印到标准错误
允许使用零秒的重试间隔, 这可能导致客户端陷入忙循环并无谓地消耗 CPU
不要与第三方分享 OAuth 流程流量的输出。 它包含可以被用来攻击你的客户端和服务器的秘密。
[11]
在
PQAUTHDATA_OAUTH_BEARER_TOKEN
钩子回调期间执行阻塞操作,
会干扰诸如
PQconnectPoll
之类的非阻塞连接 API,
并阻止并发连接取得进展。
只使用同步连接原语
(例如
PQconnectdb)
的应用程序,
可以改为在钩子期间同步地检索一个令牌,
而不实现
async
回调,
但它们必然会被限制为一次一条连接。