前言
在整个 MySQL client connection 生命周期中,鉴权过程位于第一步。鉴权完成后,连接进入 command phase,client 才能继续向 server 发送 command。本文聚焦 client 连接时 user/password 的认证过程。
本文内容基于 MySQL Community 8.0.33。
鉴权过程概览
MySQL 支持多种鉴权插件,常见有 native_password_authenticate、caching_sha2_password_authenticate。本文先以 native_password 为主线,再补充插件切换场景。
native_password 鉴权主流程
mysqld_main()
...
connection_event_loop()
process_new_connection()
thd_prepare_connection(thd)
check_connection()
acl_authenticate() // 鉴权
while(thd alive) {
do_command(); // 鉴权成功后进入 command phase
}
连接鉴权交互
- Server 发送 initial Handshake(包含 scramble 与 plugin 信息)
- Client 基于 plugin 对 scramble 处理后返回 Handshake Response
- Server 校验结果,成功后进入 command phase

server 发送 initial Handshake
acl_authenticate()
auth_plugin_name = default_auth_plugin_name
mpvio.scramble[SCRAMBLE_LENGTH] = 1
do_auth_once()
prepare plugin
auth->authenticate_user()
if (mpvio->scramble[SCRAMBLE_LENGTH])
generate_user_salt()
mpvio->write_packet()
if (mpvio->packets_written == 0)
send_server_handshake_packet()
首次包会构建 Protocol::HandshakeV10,核心字段包括 protocol version、server version、scramble、auth_plugin_name。

client 接收 Handshake 并发送 Response
csm_read_greeting
csm_parse_handshake
csm_authenticate()
run_plugin_auth()
authsm_begin_plugin_auth()
auth_plugin->authenticate_user()
vio->read_packet()
scramble(server_scramble_data, mysql->passwd)
vio->write_packet()
prep_client_reply_packet
my_net_write() && net_flush()
client 发送时构建 Protocol::HandshakeResponse41,关键字段包括 client_flag、auth_response、client_plugin_name。

server 接收 Handshake Response
acl_authenticate()
do_auth_once()
authenticate_user()
mpvio->read_packet()
parse_client_handshake_packet()
if (check_scramble)
return OK
校验成功后,连接进入 command phase。
切换鉴权插件场景
在 MySQL 8.0.33 中,默认插件通常是 caching_sha2_password。若某个用户实际插件不同(例如 native_password),server 会在首轮响应后发送 Authentication Switch Request,client 切换插件并再次完成认证。

server 触发 Auth Switch Request
parse_client_handshake_packet()
if (mpvio->acl_user_plugin != mpvio->plugin)
mpvio->status = MPVIO_EXT::RESTART
if (mpvio.status == MPVIO_EXT::RESTART)
do_auth_once()
send_plugin_request_packet()
第二轮 server 发的是 Protocol::AuthSwitchRequest。

client 处理 Auth Switch Request
if (mysql->net.read_pos[0] == 254) // AuthSwitchRequest
ctx->auth_plugin_name = server_send_plugin_name
authenticate_user()
mpvio->read_packet() // 读取新的 scramble
mpvio->write_packet() // 回传 AuthSwitchResponse
client 回传的是 Protocol::AuthSwitchResponse,不再是 Handshake Response。

server 接收 Auth Switch Response
acl_authenticate()
do_auth_once()
mpvio->read_packet()
*buf = protocol->get_net()->read_pos
if (check_scramble)
return OK
server 验证成功后,连接同样进入 command phase。