08 协议解析-连接阶段

image

MySQL协议是一个有状态的协议。

  1. 当一个(TCP)连接建立后,服务器会发起Connection Phase
  2. 连接阶段完成后进入Command Phase,(TCP)连接终止命令阶段结束。
  3. 如果发送复制命令,那么可以从连接阶段进入Replication Protocol

image

Connection Phase

在连接阶段执行一下任务:

  • 交换客户端和服务器的功能
  • 如果需要,设置SSL通信通道
  • 针对服务器对客户端进行身份验证

从客户端连接服务器开始,服务器可以发送一个ERR数据包来结束握手或者发生一个初始Handshake数据包,客户端收到后会回复一个Handshake响应包。在此阶段,客户端可以请求SSL连接,在这种情况下,必须在客户端发送身份验证响应前建立SSL通信通道。

如果服务器将ERR数据包作为第一个数据包发送,那么这个发送行为是在客户端和服务器协商任何功能之前发生的。因此,ERR数据包将不包含SQL状态。

初次握手后,服务器将通知客户端有关身份验证的方法(除非在握手期间已经确定该方法),并且身份验证交换继续进行,直到服务器发送OK_Packet接受连接或者发送ERR_Packet拒绝连接。

image

初次握手

初次握手从服务器发送Protocol::Handshake数据包开始。在这之后,客户端可以选择使用Protocol::SSLRequest数据包请求建立SSL连接,然后客户端发送Protocol::HandshakeResponse数据包。

普通握手

  1. 服务器发送Protocol::Handshake
  2. 客户端回复Protocol::HandshakeResponse

image

SSL握手

  1. 服务器发送Protocol::Handshake
  2. 客户端返回Protocol::SSLRequest
  3. 通常通过SSL交换来建立SSL连接(这个过程挺消耗资源的)
  4. 客户端发送Protocol::HandshakeResponse

image

功能协商

为了允许旧客户端连接到新服务器,Protocol::Handshake包含MySQL服务器版本即服务器的功能标志。

客户端在Protocol::HandshakeResponse中只声明与服务器相同的功能。

然后使用如下参数达成一致:

  • 状态标识(status flags)
  • SQL状态错误码(SQL states for error codes)
  • 认证方法(authentication methods)
  • SSL(SSL Support)
  • 压缩(Compression)

确定身份验证的方法

用于身份验证的方法将与用户帐户绑定,并存储在mysql.user表的plugin列中。客户端在Protocol::HandshakeResponse数据包中发送将要登录的用户帐户。这样,服务器就能查找mysql.user表并找到要使用的身份验证方法。

为节省通信成本,服务器和客户端在发送初始Handshake数据包时就对要使用的身份验证方法进行了推测。

服务器使用其默认身份验证方法default_auth_plugin生成初始身份验证数据的有效负载,并将其与方法名放在Protocol::Handshake中一起发给客户端。

客户端在Protocol::HandshakeResponse中包含对服务器发送的身份验证数据的答复。

当在Protocol::HandshakeResponse中包括身份验证回复时,客户端没有义务使用与Protocol::Handshake数据包中服务器所使用的身份验证方法相同的身份验证方法。客户端使用的身份验证方法的名称存储在响应数据包中。如果客户端或服务器在初始握手中包换的推测身份验证方法不正确,则服务器会使用Protocol::AuthSwitchRequest通知客户端应使用哪种身份验证方法。

在MySQL 4.0之前,MySQL协议仅支持Old Password Authentication。在MySQL 4.1中,添加了Native Authentication方法,而在MySQL 5.5中,可以通过身份验证插件实现任意身份验证方法。

如果客户端或服务器不支持插件式身份验证(即未设置CLIENT_PLUGIN_AUTH功能标志),则从客户端和服务器功能继承使用的身份验证方法,如下所示:

  • 如果未设置CLIENT_PROTOCOL_41CLIENT_SECURE_CONNECTION,则使用的方法是Old Password Authentication
  • 如果同时设置了CLIENT_PROTOCOL_41CLIENT_SECURE_CONNECTION,但未设置CLIENT_PLUGIN_AUTH,则使用的方法是Native Authentication

快速身份验证路径

假设客户端要通过用户帐户U登录,并且该用户帐户已定义为使用身份验证方法server_method。在以下情况下使用快速身份验证路径:

  • 服务器使用server_method生成身份验证数据后放入Protocol::Handshake数据包中。
  • 客户端在Protocol::HandshakeResponse中声明使用client_authentication_method,该方法与服务器使用的server_method兼容。

这样在握手期间就已经开始了第一轮身份验证。然后,根据身份验证方法server_method,进一步交换身份验证,直到服务器接受或拒绝身份验证为止。

验证成功

成功执行快速身份验证路径的步骤如下:

  1. 客户端连接服务器
  2. 服务器发送Protocol::Handshake
  3. 客户端响应Protocol::HandshakeResponse
  4. 客户端和服务器根据服务器身份验证方法的要求,为客户端尝试进行身份验证的用户帐户交换其他数据包
  5. 服务器响应OK_Packet

image

服务器在步骤4中发送一个Protocol::AuthMoreData数据包,其前缀为0x01,来区别于ERR_PacketOK_Packet

注意:许多身份验证方法(包括mysql_native_password方法)由单个请求-响应交换组成。因此,在步骤4中不会交换任何额外的数据包,并且服务器在接收到Protocol::HandshakeResponse数据包后(如果身份验证成功)就直接发送OK_Packet

验证失败

它与身份验证成功完全一样,只是在用户身份验证失败的时候,服务器将以ERR_Packet而不是OK_Packet进行回复。

image

验证方法不匹配

假设客户端要以用户U身份登录,并且该用户帐户使用身份验证方法M。如果:

  1. 服务器用于生成放在Protocol::Handshake数据包中的身份验证有效负载的默认方法与M不同
  2. 客户端用于生成放在Protocol::HandshakeResponse数据包中的方法与M不兼容

则说明身份验证方法不匹配,必须使用正确的身份验证方法重新启动身份验证交换过程。

注意:

  1. 即使客户端和服务器在初始握手中使用了兼容的身份验证方法,也可能发生不匹配,因为,服务器使用的方法与用户帐户所需的方法不同。
  2. 在4.1-5.7版本的服务器和客户端中,默认身份验证方法是Native Authentication
  3. 在8.0版本的服务器和客户端中,默认的身份验证方法是Caching_sha2_password information
  4. 客户端和服务器可以通过--default-auth选项更改其默认身份验证方法。
  5. 对客户端来说,查看在Protocol::Handshake数据包中声明的服务器的默认身份验证方法,并从中推断出身份验证方法,比在生成Protocol::HandshakeResponse数据包时直接使用客户端默认身份验证方法更好。但是,由于服务器和客户端之间存在一对多的身份验证方法插件,而且,客户端通常都不知道这种映射关系,因此这在mysql客户端库中未实现。

如果发生身份验证方法不匹配,服务器将向客户端发送Protocol::AuthSwitchRequest数据包,其中包含服务器要使用的身份验证方法的名称以及使用新方法重新生成的第一个身份验证有效负载。客户端应切换到服务器请求的身份验证方法,并按照该方法的指示继续进行交换。

如果客户端不知道所请求的方法,则应断开连接。

变更验证方法

  1. 客户端连接服务器
  2. 服务器发送Protocol::Handshake
  3. 客户端响应Protocol::HandshakeResponse
  4. 服务器发送Protocol::AuthSwitchRequest来告知客户端需要更换一个新的验证方法
  5. 客户端和服务器根据服务器身份验证方法的需要,为客户端尝试进行身份验证的用户帐户交换其他数据包
  6. 服务器响应OK_Packet或者ERR_Packet表示拒绝

image

客户端功能不满足

如果服务器发现客户端功能不足以完成身份验证,则服务器将使用ERR_Packet拒绝。在以下情况下可能会发生这种情况:

  • 不支持插件式身份验证(未设置CLIENT_PLUGIN_AUTH标志)的客户端连接到使用与Native Authentication方法不同的帐户
  • 不支持安全身份验证(未设置CLIENT_SECURE_CONNECTION标志)的客户端尝试建立连接
  • 服务器的默认身份验证方法(用于在Protocol::Handshake数据包中生成身份验证数据)与Native Authentication不兼容,并且客户端不支持插件式身份验证(未设置CLIENT_PLUGIN_AUTH标志)

在以上任何一种情况下,身份验证阶段都将如下所示:

  1. 客户端连接服务器
  2. 服务器发送Protocol::Handshake
  3. 客户端响应Protocol::HandshakeResponse
  4. 服务器发现客户端没有足够的功能来处理请求的验证方法,然后发生ERR_Packet并关闭连接

image

客户端未知的新验证方法

即便客户端支持外部(插件式)身份验证(设置了CLIENT_PLUGIN_AUTH标志),也可能不知道Protocol::AuthSwitchRequest数据包中声明的新的身份验证方法。在这种情况下,客户端只需断开连接即可。

  1. 客户端连接服务器
  2. 服务器发送Protocol::Handshake
  3. 客户端响应Protocol::HandshakeResponse
  4. 服务器发送Protocol::AuthSwitchRequest来告知客户端需要更换一个新的验证方法
  5. 客户端发现它不知道服务器请求的身份验证方法,然后,断开连接

image

Non-CLIENT_PLUGIN_AUTH客户端

注意:这只会在8.0版本之前的服务器上发生。 8.0版本开始移除了Old Password Authentication

服务器将向未设置CLIENT_PLUGIN_AUTH标志的客户端请求更改身份验证方法的唯一可能是满足一下条件:

  1. 客户端在Protocol::HandshakeResponse数据包中使用Old Password Authentication
  2. 客户端支持安全身份验证(已设置CLIENT_SECURE_CONNECTION
  3. 服务器的默认身份验证方法是Native Authentication

在这种情况下,服务器发送Protocol::OldAuthSwitchRequest数据包,其中不包含新的验证方法的名称,因为它被隐式假定为Native Authentication,并且其中不包含身份验证数据。 客户端响应Protocol::HandshakeResponse320。要生成密码哈希,客户端必须重用服务器在Protocol::Handshake中发送的随机字节。

image

执行COM_CHANGE_USER指令后的验证

在命令阶段,客户端可以发送COM_CHANGE_USER命令,该命令将通过完全身份验证握手来触发对新帐户的身份验证。

与连接阶段类似,服务器可以使用ERR_PacketOK_Packet来响应快速身份验证路径,或者发送Protocol::AuthSwitchRequest数据包进行响应,在该数据包中包含要用于新帐户的身份验证方法以及客户端要使用的第一个身份验证数据的有效负载 。根据新帐户的身份验证方法的定义,执行进一步的握手。最终,服务器将接受新帐户并响应OK_Packet,或者发生ERR_Packet来拒绝这种比变更并断开连接。

  1. 客户端发送COM_CHANGE_USER数据包
  2. 服务器响应Protocol::AuthSwitchRequest,来使用正确的身份验证方法启动身份验证握手
  3. 客户端和服务器根据新帐户的身份验证方法的要求交换其他数据包
  4. 服务器以OK_Packet响应并返回命令阶段或以ERR_Packet相应并关闭连接

image

COM_CHANGE_USERNon-CLIENT_PLUGIN_AUTH客户端

不支持可插件式身份验证的客户端可以为使用Native AuthenticationOld Password Authentication验证的账户发送COM_CHANGE_USER命令。在这种情况下,假定服务器已经发送了身份验证质询(与客户端第一次连接时发送的身份质询相同),并且客户端对该质询的答复(即新密码的哈希)应在发送auth_response中包含 COM_CHANGE_USER字段。

  1. 客户端发送COM_CHANGE_USER数据包,其中包含Native Authentication(4.1版后的客户端)或Old Password Authentication(4.1版之前的客户端)方法的身份验证响应(即密码的哈希值)
  2. 服务器以OK_Packet响应并返回到命令阶段,或者以ERR_Packet返回并关闭连接

image

与正常连接期间一样,不支持插件式身份验证的4.1版客户端也有可能连接到使用Old Password Authentication的帐户,在这种情况下,服务器将发送Protocol::OldAuthSwitchRequest并期望客户端以Protocol::HandshakeResponse320来响应。

  1. 客户端发送COM_CHANGE_USER数据包来响应Native Authentication
  2. 服务器响应Protocol::OldAuthSwitchRequest(0xFE字节)
  3. 客户端再次以Old Password Authentication所需的形式发送响应
  4. 服务器以OK_Packet响应并返回到命令阶段或以ERR_Packet返回并断开连接

image

上次修改: 18 May 2020