具体详细参考:dev.mysql.com/doc/dev/mys...
这个阶段是客户端连接到 MySQL 服务器的核心过程,涉及能力协商、安全通道建立和用户身份验证。
核心目标: 建立一条安全、功能匹配、经过身份验证的客户端与服务器之间的通信通道。
连接阶段的主要任务:
- 能力交换 (Capability Exchange): 客户端和服务器互相告知对方支持哪些功能和选项(如是否支持 SSL、是否支持认证插件、最大数据包大小等)。
- SSL 通信通道设置 (SSL Communication Setup - Optional): 如果客户端请求并且服务器支持,在此阶段建立一个加密的 SSL/TLS 通信通道。这发生在发送实际认证响应之前。
- 客户端身份验证 (Client Authentication): 服务器根据其用户账户系统(主要是
mysql.user
表)验证客户端提供的凭据(通常是用户名和密码)。
连接阶段流程详解:
-
初始连接 (Client Connect):
- 客户端发起一个 TCP/IP(或 Unix Socket)连接到 MySQL 服务器。
-
服务器初始响应 (Server Initial Response):
- 选项 A (错误 ERR_Packet): 服务器可能立即返回一个
ERR_Packet
(例如,服务器过载、连接数满、IP 被拒绝)。因为没有进行任何协商,这个错误包可能不包含标准的 SQL 状态码。连接立即终止。 - 选项 B (初始握手包 Initial Handshake Packet / Protocol::Handshake): 这是正常流程的开始。服务器发送一个包含以下关键信息的包:
- 协议版本: 服务器使用的协议版本号。
- 服务器版本: MySQL 服务器版本字符串。
- 连接 ID: 服务器分配给此连接的唯一标识符。
- 随机挑战值 (Scramble): 一串随机字节,用于后续的认证过程(防止重放攻击)。
- 服务器能力标志 (Server Capability Flags): 指示服务器支持的功能(如
CLIENT_SSL
,CLIENT_PLUGIN_AUTH
,CLIENT_SECURE_CONNECTION
,CLIENT_PROTOCOL_41
)。 - 服务器字符集: 服务器默认字符集。
- 服务器状态标志: 服务器当前状态。
- 初始认证数据 (Authentication Payload): 基于服务器的默认认证插件 (
default_auth_plugin
) 生成的一段数据(例如,一个挑战值)。 - 默认认证插件名称: 服务器默认使用的认证方法名称(如
caching_sha2_password
,mysql_native_password
)。
- 选项 A (错误 ERR_Packet): 服务器可能立即返回一个
-
客户端响应 (Client Handshake Response):
- SSL 请求 (Protocol::SSLRequest - Optional): 如果客户端在服务器的能力标志中看到
CLIENT_SSL
被设置,并且它希望使用 SSL,它会先发送一个Protocol::SSLRequest
包(包含客户端能力标志)。 - SSL 通道建立 (Plain Handshake - If Requested): 如果上一步发送了
SSLRequest
,客户端和服务器将执行标准的 SSL/TLS 握手过程,在 TCP 连接之上建立一个加密通道。后续所有通信都通过此加密通道进行。 - 握手响应包 (Protocol::HandshakeResponse): 客户端发送此包,包含:
- 客户端能力标志 (Client Capability Flags): 客户端实际使用的功能子集(是客户端能力与服务器在
Protocol::Handshake
中宣布的能力的交集)。 - 最大数据包大小: 客户端能接收的最大包大小。
- 客户端字符集: 客户端使用的字符集。
- 用户名: 客户端尝试登录的用户名。
- 认证响应数据 (Authentication Response): 客户端根据它认为 或决定 使用的认证方法对服务器发来的初始挑战值(和密码)进行计算后生成的一段数据(例如,密码的加盐哈希)。客户端决定使用的认证方法名称也会包含在此包中(可能与服务器默认方法不同!)。
- 数据库名称 (Optional): 如果客户端指定了默认数据库(且服务器能力支持)。
- 其他信息: 如认证插件名称(如果客户端支持
CLIENT_PLUGIN_AUTH
)。
- 客户端能力标志 (Client Capability Flags): 客户端实际使用的功能子集(是客户端能力与服务器在
- SSL 请求 (Protocol::SSLRequest - Optional): 如果客户端在服务器的能力标志中看到
认证阶段:核心挑战与流程
认证的核心是验证客户端是否知道与指定用户名对应的密码。难点在于服务器在收到 HandshakeResponse
之前并不知道客户端要登录哪个用户(mysql.user
表里的 plugin
列定义该用户的认证方法)。这导致了"乐观猜测 "和潜在的"认证方法不匹配"。
- 能力协商 (Capability Negotiation): 通过交换能力标志 (
CLIENT_PROTOCOL_41
,CLIENT_SECURE_CONNECTION
,CLIENT_PLUGIN_AUTH
),客户端和服务器确定它们共同支持的最低协议版本和认证方式(旧密码认证、本地认证、插件认证)。 - 确定认证方法 (Determining Authentication Method):
- 服务器在
Protocol::Handshake
中使用其默认认证插件 (default_auth_plugin
) 生成初始挑战值。 - 客户端在
Protocol::HandshakeResponse
中指定它决定使用 的认证方法(通常就是它从服务器Handshake
中看到的默认方法)并生成对应的响应。 - 服务器在收到
HandshakeResponse
中的用户名 后,才能查找mysql.user
表,找到该用户账户实际配置的认证方法 (user.plugin
)。 - 此时可能出现不一致:
- 服务器初始挑战使用的默认方法 (
default_auth_plugin
) != 用户实际配置的方法 (user.plugin
)。 - 客户端响应时使用的方法 != 用户实际配置的方法 (
user.plugin
)。
- 服务器初始挑战使用的默认方法 (
- 服务器在
后续认证流程(基于匹配结果):
-
快速认证路径 (Fast Path - 乐观猜测成功):
- 条件: 服务器
Handshake
使用的默认方法 (default_auth_plugin
) 恰好等于 用户实际配置的方法 (user.plugin
),并且 客户端在HandshakeResponse
中正确使用了该方法(或者兼容方法)进行响应。 - 流程:
- 客户端连接 -> 服务器发送
Protocol::Handshake
-> (可选SSL) -> 客户端发送Protocol::HandshakeResponse
-> 服务器验证响应。 - 如果验证成功: 服务器直接发送
OK_Packet
,连接建立成功,进入命令阶段。(对于像mysql_native_password
这样的单步方法,这通常是最终结果)。 - 如果验证失败: 服务器发送
ERR_Packet
,连接终止。 - 如果需要更多数据 (Multi-step Auth): 如果认证方法需要多轮交互(如
sha256_password
可能需要公钥加密交换),服务器会发送Protocol::AuthMoreData
包(以0x01
开头,区别于ERR_Packet
的0xFF
),客户端回复相应数据,直到服务器发送OK_Packet
或ERR_Packet
。
- 客户端连接 -> 服务器发送
- 条件: 服务器
-
认证方法不匹配 (Authentication Method Mismatch - 最常见的问题场景):
- 触发条件: 服务器初始挑战使用的默认方法 (
default_auth_plugin
) 不等于 用户实际配置的方法 (user.plugin
),或者 客户端在HandshakeResponse
中使用的认证方法 不兼容 于用户实际配置的方法 (user.plugin
)。 - 服务器动作 (Protocol::AuthSwitchRequest): 服务器检测到不匹配后,立即向客户端发送一个
Protocol::AuthSwitchRequest
包,明确告诉客户端:- **
auth_plugin_name
: 它 必须**使用哪个认证插件/方法(即用户实际配置的user.plugin
)。 - **
auth_plugin_data
: 该方法对应的新初始挑战值/数据**。
- **
- 客户端动作:
- 如果客户端认识并支持新方法: 它立即切换,使用指定的新方法和新挑战值(以及用户密码)计算一个新的认证响应,并发送一个
Protocol::AuthSwitchResponse
包给服务器。 - 如果客户端不认识或不支持新方法: 它直接断开连接。
- 如果客户端认识并支持新方法: 它立即切换,使用指定的新方法和新挑战值(以及用户密码)计算一个新的认证响应,并发送一个
- 后续流程: 从
AuthSwitchResponse
开始,认证流程按照新指定的认证方法 继续进行。可能需要多个AuthMoreData
/AuthSwitchResponse
回合,最终服务器发送OK_Packet
(成功) 或ERR_Packet
(失败)。
- 触发条件: 服务器初始挑战使用的默认方法 (
-
客户端能力不足 (Insufficient Client Capabilities - 服务器直接拒绝):
- 触发条件: 客户端不支持插件式认证 (
CLIENT_PLUGIN_AUTH
标志未设置),但遇到以下情况之一:- 用户账户配置了非原生的认证方法(如
caching_sha2_password
,authentication_ldap_sasl
)。 - 服务器
Handshake
中使用的默认方法 (default_auth_plugin
) 与Native Authentication
不兼容(不太常见)。
- 用户账户配置了非原生的认证方法(如
- 服务器动作: 服务器在识别到客户端能力不足以处理所需的认证方法后(通常在解析
HandshakeResponse
能力标志或用户名后),直接发送ERR_Packet
并关闭连接 。没有AuthSwitchRequest
的机会。
- 触发条件: 客户端不支持插件式认证 (
-
特殊案例:非插件客户端 (Non-CLIENT_PLUGIN_AUTH Clients) 与旧密码认证切换:
- 场景: 一个支持
CLIENT_PROTOCOL_41
和CLIENT_SECURE_CONNECTION
但不支持CLIENT_PLUGIN_AUTH
的"现代"客户端(如 MySQL 5.0 客户端)尝试连接一个配置为使用旧密码认证 (mysql_old_password
) 的用户账户。 - 服务器动作: 服务器发送一个特殊的
Protocol::OldAuthSwitchRequest
包(不包含新插件名,隐式要求切换到旧密码认证)。 - 客户端动作: 客户端应使用旧密码认证算法 和服务器在初始
Handshake
中发送的随机挑战值重新计算密码哈希,并发送一个Protocol::HandshakeResponse320
包(使用旧格式)。 - 后续流程: 服务器验证旧哈希,回复
OK_Packet
或ERR_Packet
。
- 场景: 一个支持
连接阶段后的身份验证:COM_CHANGE_USER
- 在命令阶段 (Command Phase),客户端可以发送
COM_CHANGE_USER
命令来切换当前连接使用的用户账户(无需断开重连)。 - 流程: 类似于初始连接阶段:
- 客户端发送
COM_CHANGE_USER
包(包含新用户名、新密码的认证响应(基于初始挑战值)、新数据库等)。 - 服务器根据新用户名查找
mysql.user
表,找到该用户的实际认证方法 (user.plugin
)。 - 如果匹配或兼容: 服务器验证响应,发送
OK_Packet
(成功)或ERR_Packet
(失败)。 - 如果不匹配: 服务器发送
Protocol::AuthSwitchRequest
(或Protocol::OldAuthSwitchRequest
),要求客户端切换到正确的认证方法并重新认证。后续流程与连接阶段相同(AuthSwitchResponse
,AuthMoreData
等),最终OK_Packet
或ERR_Packet
。
- 客户端发送
- 非插件客户端的限制: 非插件客户端只能切换到使用原生认证 (
mysql_native_password
) 或旧密码认证 (mysql_old_password
) 的用户。如果需要切换的账户使用其他方法,服务器会直接拒绝(发送ERR_Packet
或客户端无法处理AuthSwitchRequest
)。
总结:
MySQL 连接阶段是一个精心设计的协议,通过能力交换、SSL 协商和复杂的认证流程(包括乐观猜测和处理认证方法不匹配)来建立安全可靠的客户端-服务器连接。理解 Protocol::Handshake
, Protocol::HandshakeResponse
, Protocol::AuthSwitchRequest
, Protocol::AuthSwitchResponse
, ERR_Packet
和 OK_Packet
这些核心数据包以及 CLIENT_PLUGIN_AUTH
等能力标志对于诊断连接和认证问题至关重要。COM_CHANGE_USER
复用连接阶段的机制实现了高效的会话内用户切换。