在实现验证器模块之前,请阅读并理解本节的全部内容。 故障的验证器可能比没有身份验证更糟,因为它提供了虚假的安全感, 并且可能会对 OAuth 生态系统中的其他部分造成攻击。
尽管不同的模块可能采用非常不同的令牌验证方法, 但实现通常需要执行三个独立的操作:
验证器必须首先确保所提供的令牌实际上是用于客户端身份验证的有效持有者令牌。 正确的方法取决于提供者,但通常涉及加密操作以证明令牌是由受信方创建的 (离线验证),或者将令牌呈现给该受信方,以便其为您执行验证 (在线验证)。
在线验证通常通过 OAuth 令牌 反思 实现,要求验证器模块的步骤更少, 并允许在令牌被盗或错误发行的情况下进行集中撤销。 但是,它确实要求模块在每次身份验证尝试中至少进行一次网络调用 (所有调用必须在配置的 authentication_timeout 内完成)。 此外,您的提供者可能不提供供外部资源服务器使用的反思端点。
离线验证要复杂得多,通常需要验证器维护一个提供者的受信签名密钥列表,然后检查令牌的加密签名及其内容。 实现必须严格遵循提供者的指示,包括对发行者(“这个令牌来自哪里?”)、受众(“这个令牌是给谁的?”)和有效期(“这个令牌何时可以使用?”)的任何验证。 由于模块与提供者之间没有通信,因此无法通过此方法集中撤销令牌; 离线验证器实现可能希望对令牌的有效期最大长度施加限制。
如果令牌无法验证,模块应立即失败。 如果持有者令牌不是由受信方发出的,进一步的身份验证/授权是毫无意义的。
接下来,验证器必须确保最终用户已授权客户端代表他们访问服务器。 这通常涉及检查已分配给令牌的范围,以确保它们涵盖当前 HBA 参数的数据库访问。
此步骤的目的是防止 OAuth 客户端在虚假前提下获取令牌。如果验证器要求所有令牌都携带涵盖数据库访问的范围,则提供者应在流程中大声提示用户授予该访问权限。这使他们有机会拒绝请求,如果客户端不应使用他们的凭据连接到数据库。
虽然可以通过使用已部署架构的带外知识来建立客户端授权,而不需要显式范围,但这样做会将用户排除在外,防止他们发现部署错误,并允许任何此类错误被静默利用。对数据库的访问必须严格限制为仅受信客户端 [17] 如果用户没有被提示额外的范围。
即使授权失败,模块也可以选择继续从令牌中提取身份验证信息,以用于审计和调试。
最后,验证器应确定令牌的用户标识符,可以通过向提供者请求此信息或从令牌本身提取来实现,并将该标识符返回给服务器(服务器随后将使用 HBA 配置做出最终授权决策)。该标识符将在会话中通过
system_user
可用,并在启用时记录在服务器日志中 log_connections。
不同的提供者可能会记录各种不同的身份验证信息,通常称为 声明。提供者通常会记录哪些声明足够可信以用于授权决策,哪些则不然。(例如,使用最终用户的全名作为身份验证标识符可能并不明智,因为许多提供者允许用户任意更改其显示名称。)最终,使用哪个声明(或声明组合)的选择取决于提供者的实现和应用程序的要求。
请注意,通过启用用户映射委托,匿名/假名登录也是可能的;请参见 第 50.1.3 节。
开发人员在实现令牌验证时应牢记以下事项:
模块不应将令牌或令牌的一部分写入服务器日志。即使模块认为令牌无效,这一点也是成立的;一个使客户端与错误提供者通信的攻击者不应能够从磁盘中检索到该(否则有效的)令牌。
通过网络发送令牌的实现(例如,为了与提供者进行在线令牌验证)必须对等认证,并确保使用强大的传输安全性。
模块可以使用与标准扩展相同的 日志记录设施;然而,在连接的身份验证阶段,向客户端发出日志条目的规则略有不同。一般来说,模块应在 COMMERROR 级别记录验证问题并正常返回,而不是使用 ERROR/FATAL 来展开堆栈,以避免向未认证的客户端泄露信息。
模块必须保持可被信号中断,以便服务器能够正确处理身份验证超时和来自 pg_ctl 的关闭信号。例如,套接字上的阻塞调用通常应替换为处理套接字事件和中断而不发生竞争的代码(参见 WaitLatchOrSocket()、WaitEventSetWait() 等),而长时间运行的循环应定期调用 CHECK_FOR_INTERRUPTS()。未遵循此指导可能导致后端会话无响应。
对 OAuth 系统的测试范围远超本文件的范围,但至少应考虑负面测试为强制性。设计一个让授权用户进入的模块是微不足道的;系统的整个目的在于将未授权用户拒之门外。
验证器实现应记录报告给服务器的每个最终用户的认证 ID 的内容和格式,因为 DBA 可能需要使用此信息来构建 pg_ident 映射。(例如,它是电子邮件地址吗?组织 ID 号码?UUID?)他们还应记录在 delegate_ident_mapping=1 模式下使用该模块是否安全,以及为此需要什么额外的配置。
验证模块的标准交付物是用户标识符,服务器将根据该标识符与任何配置的
pg_ident.conf
映射进行比较,以确定最终用户是否被授权连接。
然而,OAuth 本身就是一个授权框架,令牌可能携带有关用户权限的信息。
例如,令牌可能与用户所属的组织组相关联,或列出用户可以承担的角色,
将这些知识复制到每个服务器的本地用户映射中可能并不理想。
要完全绕过用户名映射,并让验证模块承担授权用户连接的额外责任, 可以使用 delegate_ident_mapping 配置 HBA。 然后,模块可以使用令牌范围或等效方法来决定用户是否被允许以其期望的角色连接。 用户标识符仍将由服务器记录,但在决定是否继续连接时并不发挥作用。
使用此方案,身份验证本身是可选的。只要模块报告连接已被授权, 即使没有记录的用户标识符,登录也将继续。这使得实现对数据库的匿名或假名访问成为可能, 第三方提供者执行所有必要的身份验证,但不向服务器提供任何用户识别信息。 (一些提供者可能会创建一个可以记录的匿名 ID 号码,以便后续审计。)
用户映射委托提供了最大的架构灵活性,但它使验证模块成为连接 授权的单点故障。使用时请谨慎。
[17] 也就是说,“受信”是指 OAuth 客户端和 PostgreSQL 服务器由同一实体控制。值得注意的是,libpq 支持的设备授权客户端流程通常不符合这一标准,因为它是为公共/不受信客户端使用而设计的。