SASL 是一种面向连接协议中的认证框架。 目前,PostgreSQL 实现了三种 SASL 认证机制:SCRAM-SHA-256、SCRAM-SHA-256-PLUS 和 OAUTHBEARER。将来可能会增加更多机制。下面的步骤说明了 SASL 认证的一般执行方式,后续小节则给出特定机制的更多细节。
SASL 认证消息流
要开始一次 SASL 认证交换,服务器会发送一条 AuthenticationSASL 消息。其中包含一个按服务器优先顺序排列的、 服务器可接受的 SASL 认证机制列表。
客户端从该列表中选择一个受支持的机制, 并向服务器发送一条 SASLInitialResponse 消息。 该消息包括所选机制的名称,以及(如果所选机制使用的话) 一个可选的初始客户端响应(Initial Client Response)。
接下来会有一或多轮服务器挑战与客户端响应的消息。 每个服务器挑战都以一条 AuthenticationSASLContinue 消息发送, 客户端则以一条 SASLResponse 消息响应。 这些消息的具体细节因机制而异。
最后,当认证交换成功完成时,服务器会发送一条可选的 AuthenticationSASLFinal 消息,并紧跟一条 AuthenticationOk 消息。 AuthenticationSASLFinal 包含额外的从服务器到客户端的数据, 其内容由所选的认证机制决定。如果认证机制在完成时 不使用额外数据,则不会发送 AuthenticationSASLFinal 消息。
出错时,服务器可以在任何阶段中止认证, 并发送一条 ErrorMessage。
SCRAM-SHA-256 及其带信道绑定的变体
SCRAM-SHA-256-PLUS 是基于口令的认证机制。
它们在 RFC 7677
和 RFC 5802
中有详细描述。
当在 PostgreSQL 中使用 SCRAM-SHA-256 时,
服务器会忽略客户端在 client-first-message
中发送的用户名,而使用启动消息中已经发送过的用户名。
PostgreSQL 支持多种字符编码,
而 SCRAM 规定用户名必须使用 UTF-8,
因此可能无法用 UTF-8 表示 PostgreSQL 的用户名。
SCRAM 规范规定口令也必须是 UTF-8,并且要经过 SASLprep 算法处理。 不过 PostgreSQL 并不要求口令使用 UTF-8。 在设置用户口令时,无论实际使用的编码是什么, 都会把它当作 UTF-8 用 SASLprep 处理。 但如果它不是一个合法的 UTF-8 字节序列, 或者它包含被 SASLprep 算法禁止的 UTF-8 字节序列, 则会直接使用原始口令而不做 SASLprep 处理,而不是抛出错误。 这使得口令在是 UTF-8 时可以被规范化, 同时仍然允许使用非 UTF-8 的口令, 并且不要求系统知道口令使用的是哪种编码。
支持 SSL 的 PostgreSQL 构建支持
信道绑定(channel binding)。
带信道绑定的 SCRAM 的 SASL 机制名称是
SCRAM-SHA-256-PLUS。PostgreSQL 使用的
信道绑定类型是 tls-server-end-point。
在不带信道绑定的 SCRAM 中, 服务器会选择一个随机数发送给客户端, 让它与用户提供的口令一起混入传输的口令哈希中。 虽然这能防止口令哈希在以后的会话中被成功重放, 但它无法阻止位于真实服务器与客户端之间的假冒服务器 把服务器的随机值传递过去并成功完成认证。
带信道绑定的 SCRAM 通过把服务器证书的 签名混入传输的口令哈希来防范这类中间人攻击。 假冒服务器虽然可以重传真实服务器的证书, 但它没有与该证书匹配的私钥,因此无法证明自己是证书的所有者, 从而导致 SSL 连接失败。
实战范例
服务器发送一条 AuthenticationSASL 消息,
其中包含服务器可接受的 SASL 认证机制列表。
如果服务器在构建时启用了 SSL 支持,
该列表将是 SCRAM-SHA-256-PLUS
和 SCRAM-SHA-256,否则只有后者。
客户端通过发送一条 SASLInitialResponse 消息来响应,
其中指明所选的机制是 SCRAM-SHA-256 还是
SCRAM-SHA-256-PLUS。(客户端可以自由选择
任一机制,但为了更好的安全性,只要能够支持,
就应当选择带信道绑定的变体。)
在初始客户端响应字段中,该消息包含 SCRAM 的
client-first-message。
client-first-message 还包含
客户端选择的信道绑定类型。
服务器发送一条 AuthenticationSASLContinue 消息,
其内容是 SCRAM 的
server-first-message。
客户端发送一条 SASLResponse 消息,
其内容是 SCRAM 的
client-final-message。
服务器发送一条带有 SCRAM
server-final-message 的
AuthenticationSASLFinal 消息,并紧跟一条 AuthenticationOk 消息。
OAUTHBEARER 是一种用于联邦认证的
基于令牌的机制。它在
RFC 7628
中有详细描述。
典型的交换过程因客户端是否已为当前用户缓存了承载令牌(bearer token) 而异。如果没有,交换将通过两条连接进行: 第一条是用于从服务器获取 OAuth 元数据的“发现”连接, 第二条是在客户端取得令牌之后用于发送令牌的连接。 (libpq 的内建流程目前没有实现缓存方法,因此它使用两连接交换。)
与 SCRAM 一样,该机制由客户端发起。客户端初始响应由 SCRAM
使用的标准 “GS2” 头部组成,后跟一个
key=value 对的列表。服务器目前唯一支持的键是
auth,其中包含承载令牌。
OAUTHBEARER 还规定了客户端初始响应的三个
可选组成部分(GS2 头部的 authzid,
以及 host 和
port 键),目前服务器会忽略它们。
OAUTHBEARER 不支持信道绑定,
也没有 “OAUTHBEARER-PLUS” 机制。该机制在成功的认证过程中
不使用服务器数据,因此交换中不会使用
AuthenticationSASLFinal 消息。
实战范例
在第一次交换期间,服务器会发送一条 AuthenticationSASL 消息,
其中声明了 OAUTHBEARER 机制。
客户端通过发送一条 SASLInitialResponse 消息来响应,
其中指明 OAUTHBEARER 机制。
假设客户端当前用户尚没有有效的承载令牌,
则 auth 字段为空,表示这是一条
发现连接。
服务器发送一条 AuthenticationSASLContinue 消息,其中包含一个错误
status,以及一个知名 URI 和一些作用域,
客户端应当使用它们来执行 OAuth 流程。
客户端发送一条包含空集合(单个 0x01 字节)的
SASLResponse 消息,以完成它在发现交换中的那一半。
服务器发送一条 ErrorMessage 使第一次交换失败。
此时,客户端会利用它在配置中得到的元数据以及服务器提供的元数据,
执行多种可能的 OAuth 流程之一来获取一个承载令牌。
(这里有意保持含糊;OAUTHBEARER
并未规定或强制任何获取令牌的特定方法。)
客户端拿到令牌之后,会重新连接到服务器进行最后一次交换:
服务器会再次发送一条声明了 OAUTHBEARER
机制的 AuthenticationSASL 消息。
客户端通过再次发送一条 SASLInitialResponse 消息来响应,
但这一次消息中的 auth 字段
包含客户端流程中获得的承载令牌。
服务器按照令牌提供方的指示验证该令牌。 如果客户端有权连接,它会发送一条 AuthenticationOk 消息 来结束 SASL 交换。