在实现验证器模块之前,请阅读并理解本节的全部内容。一个有故障的验证器可能比根本没有身份验证更糟糕,因为它提供了虚假的安全感,并且可能导致针对 OAuth 生态系统中其他组件的攻击。
虽然不同的模块在令牌验证方面可能采取截然不同的方法,但实现通常需要执行三个独立的操作
验证器必须首先确保提供的令牌确实是用于客户端身份验证的有效 Bearer 令牌。正确执行此操作取决于提供者,但通常涉及加密操作以证明令牌是由受信任方创建的(离线验证),或将令牌提供给受信任方,以便它能够为您执行验证(在线验证)。
在线验证通常通过 OAuth Token Introspection 实现,它需要验证器模块的步骤更少,并允许在令牌被盗或错误颁发时进行集中吊销。但是,这需要模块在每次身份验证尝试时至少进行一次网络调用(所有调用都必须在配置的 authentication_timeout 内完成)。此外,您的提供者可能不为外部资源服务器提供内省端点。
离线验证涉及的内容要多得多,通常需要验证器维护提供者的受信任签名密钥列表,然后检查令牌的加密签名及其内容。实现必须严格遵循提供者的说明,包括任何发行者(“此令牌来自哪里?”)、受众(“此令牌是为谁准备的?”)和有效期(“此令牌何时可用?”)的验证。由于模块和提供者之间没有通信,因此无法使用此方法集中吊销令牌;离线验证器实现可能希望限制令牌有效期的最大长度。
如果无法验证令牌,模块应立即失败。如果 Bearer 令牌不是由受信任方颁发的,那么进一步的身份验证/授权就毫无意义。
接下来,验证器必须确保最终用户已授予客户端代表他们访问服务器的权限。这通常涉及检查分配给令牌的作用域,以确保它们涵盖当前 HBA 参数的数据库访问。
此步骤的目的是防止 OAuth 客户端以虚假借口获取令牌。如果验证器要求所有令牌都携带涵盖数据库访问的作用域,那么提供者应该在流程中明确提示用户授予该访问权限。这使他们有机会拒绝请求,如果客户端不应使用其凭据连接到数据库。
虽然可以通过利用部署架构的带外知识来建立没有显式作用域的客户端授权,但这会将用户排除在外,从而阻止他们发现部署错误,并允许任何此类错误被静默利用。数据库的访问权限必须严格限制给受信任的客户端 [17],如果用户没有被提示授予额外作用域。
即使授权失败,模块也可以选择继续从令牌中提取身份验证信息,用于审计和调试。
最后,验证器应确定令牌的用户标识符,方法是向提供者请求此信息或从中提取令牌本身,并将该标识符返回给服务器(然后服务器将使用 HBA 配置做出最终授权决定)。此标识符将在会话中通过 system_user
可用,如果启用了 log_connections,则会在服务器日志中记录。
不同的提供者可能会记录最终用户的各种不同身份验证信息,通常称为 声明(claims)。提供者通常会记录哪些声明值得信任以做出授权决定,哪些不值得。 (例如,使用最终用户的全名作为身份验证标识符可能不明智,因为许多提供者允许用户任意更改其显示名称。)最终,选择使用哪个声明(或声明组合)取决于提供者实现和应用程序需求。
请注意,通过启用用户映射委托,也可以实现匿名/假名登录;请参阅 第 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 本身就是一个授权框架,并且令牌可能包含有关用户特权的信息。例如,令牌可能与用户所属的组织组相关联,或列出用户可以扮演的角色,并且将这些知识复制到每个服务器的本地用户映射中可能不理想。
要完全绕过用户名映射,并让验证器模块承担授权用户连接的其他责任,HBA 可以配置为 delegate_ident_mapping。然后,该模块可以使用令牌作用域或等效方法来决定用户是否被允许以其期望的角色进行连接。服务器仍将记录用户标识符,但它在确定是否继续连接方面不起任何作用。
使用此方案,身份验证本身是可选的。只要模块报告连接已授权,即使根本没有记录用户标识符,登录也将继续。这使得实现数据库的匿名或假名访问成为可能,其中第三方提供者执行所有必要的身份验证,但不会向服务器提供任何用户标识信息。(某些提供者可能会创建一个匿名 ID 号,可以代替记录,以便以后审计。)
用户映射委托提供了最大的架构灵活性,但它将验证器模块变成了连接授权的单点故障。请谨慎使用。
[17] 即,“受信任”是指 OAuth 客户端和 PostgreSQL 服务器由同一实体控制。值得注意的是,libpq 支持的设备授权客户端流程通常不符合此标准,因为它设计用于公共/不受信任的客户端。
如果您在文档中看到任何不正确、与您对特定功能的体验不符或需要进一步说明的内容,请使用 此表单 报告文档问题。