受支持版本: 当前版本 (18)
开发版本: devel

50.1. 安全地设计验证器模块 #

Warning

在实现验证器模块之前,请先完整阅读并理解本节内容。 失效的验证器可能比完全没有认证更糟, 既会带来错误的安全感,也可能助长针对 OAuth 生态中其他组件的攻击。

50.1.1. 验证器职责 #

虽然不同模块在令牌校验上可能采用截然不同的方法, 但实现通常需要执行三个彼此独立的步骤:

验证令牌

验证器首先必须确保所提供的令牌确实是可用于客户端认证的有效 Bearer 令牌。 正确做法依赖具体提供方,但通常涉及以下两种方式之一: 通过密码学操作证明令牌由受信任方创建(离线校验), 或将令牌提交给受信任方,由其代为校验(在线校验)。

在线校验通常通过 OAuth Token Introspection 实现,验证器模块所需步骤更少, 并且在令牌被盗用或错误签发时可进行集中吊销。 但这要求模块在每次认证尝试时至少执行一次网络调用 (且都必须在配置的 authentication_timeout 内完成)。 此外,提供方可能并不提供供外部资源服务器使用的内省端点。

离线校验要复杂得多,通常要求验证器维护提供方的受信任签名密钥列表, 然后同时检查令牌的密码学签名及其内容。 实现必须严格遵循提供方说明, 包括对签发者(“该令牌来自哪里?”)、受众(“该令牌给谁用?”) 和有效期(“该令牌何时可用?”)的全部验证。 由于模块与提供方之间没有通信,使用该方法无法集中吊销令牌; 离线验证器实现可以考虑限制令牌有效期的最大长度。

如果令牌无法通过校验,模块应立即失败。 若 Bearer 令牌并非由受信任方签发,后续认证/授权都没有意义。

授权客户端

接下来,验证器必须确保终端用户已授权客户端代表其访问服务器。 这通常需要检查分配给令牌的作用域(scope), 以确认其覆盖当前 HBA 参数所要求的数据库访问权限。

该步骤的目的是防止 OAuth 客户端在虚假前提下获取令牌。 如果验证器要求所有令牌都必须携带覆盖数据库访问的作用域, 提供方在流程中就应明确提示用户授予该访问权限。 这样一来,如果客户端本不应使用其凭据连接数据库, 用户就有机会拒绝该请求。

也可以通过部署架构的带外知识在没有显式作用域的情况下完成客户端授权, 但这样会把用户排除在环路之外, 用户将无法发现部署错误,而此类错误也可能被静默利用。 如果不提示用户授予额外作用域, 则对数据库的访问必须被严格限制为仅允许受信任客户端 [17]

即使授权失败,模块也可以选择继续从令牌中提取认证信息, 用于审计和调试。

认证终端用户

最后,验证器应确定该令牌对应的用户标识符, 可通过向提供方请求此信息或直接从令牌中提取, 然后将该标识符返回给服务器 (服务器随后会基于 HBA 配置作出最终授权决策)。 该标识符可在会话中通过 system_user 获取;若启用 log_connections,也会被记录到服务器日志。

不同提供方可能记录多种不同的终端用户认证信息, 通常称为 声明(claims)。 提供方一般会说明哪些声明足够可信,可用于授权决策,哪些不行。 (例如,将终端用户全名作为认证标识符通常并不明智, 因为很多提供方允许用户任意修改显示名称。) 最终应使用哪个声明(或哪些声明的组合), 取决于提供方实现和应用需求。

还需注意,通过启用用户名映射委派也可以实现匿名/化名登录; 参见 Section 50.1.3

50.1.2. 通用编码准则 #

在实现令牌校验时,开发者应注意以下事项:

令牌机密性

模块不应将令牌或令牌片段写入服务器日志。 即使模块判定令牌无效,也应遵循此原则; 否则,诱导客户端与错误提供方通信的攻击者可能从磁盘中取回该令牌 (该令牌本身可能对正确提供方仍然有效)。

若实现需要通过网络发送令牌(例如与提供方进行在线令牌校验), 必须认证对端并确保使用强传输安全。

日志记录

模块可以使用与标准扩展相同的 日志功能; 但在连接认证阶段,向客户端发出日志条目的规则存在细微差异。 一般来说,为避免向未认证客户端泄露信息, 模块应以 COMMERROR 级别记录校验问题并正常返回, 而不是使用 ERROR/FATAL 来展开调用栈。

可中断性

模块必须保持可被信号中断, 这样服务器才能正确处理认证超时和来自 pg_ctl 的停机信号。 例如,套接字上的阻塞调用通常应替换为可无竞争地同时处理 套接字事件与中断的代码(见 WaitLatchOrSocket()WaitEventSetWait() 等), 且长时间运行的循环应定期调用 CHECK_FOR_INTERRUPTS()。 不遵循该建议可能导致后端会话失去响应。

测试

OAuth 系统测试的完整范围远超本文档讨论范围, 但至少应将负向测试视为强制要求。 设计一个允许已授权用户登录的模块很容易; 该系统的核心目的在于阻止未授权用户进入。

文档

验证器实现应记录针对每个终端用户向服务器报告的认证 ID 的内容和格式, 因为 DBA 可能需要据此构造 pg_ident 映射。 (例如,它是邮箱地址?组织 ID 号?还是 UUID?) 还应记录该模块在 delegate_ident_mapping=1 模式下是否可安全使用, 以及为此需要哪些额外配置。

50.1.3. 授权用户(用户名映射委派) #

验证器模块通常返回用户标识符, 服务器随后会将其与已配置的 pg_ident.conf 映射进行比较,以确定终端用户是否被授权连接。 不过,OAuth 本身就是授权框架, 令牌也可能携带用户权限信息。 例如,令牌可能包含用户所属组织组的信息, 或列出用户可承担的角色, 将这些信息复制到每台服务器的本地用户名映射中可能并不理想。

若要完全绕过用户名映射,并让验证器模块承担额外的用户连接授权职责, 可在 HBA 中配置 delegate_ident_mapping。 此后模块可使用令牌作用域或等效方法判断用户是否允许以其期望角色连接。 用户标识符仍会由服务器记录,但它不再参与是否继续连接的判定。

在该方案下,认证本身也是可选的。 只要模块报告连接已获授权,即使完全没有记录用户标识符,登录仍可继续。 这使得实现对数据库的匿名或化名访问成为可能, 在这种情况下,第三方提供方执行所有必要的认证, 但不向服务器提供任何可识别用户的信息。 (某些提供方可能改为提供可记录的匿名化 ID 编号,以便后续审计。)

用户名映射委派提供了最大的架构灵活性, 但也会使验证器模块成为连接授权的单点故障。 请谨慎使用。



[17] 即,“受信任”是指 OAuth 客户端与 PostgreSQL 服务器由同一实体控制。 特别地,libpq 支持的 Device Authorization 客户端流程通常不满足此条件, 因为它的设计目标是公开/非受信任客户端。

提交更正

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