文章来源:数据库内核月报 2023/05 - MySQL · 源码分析 · 鉴权过程

前言

在整个 MySQL client connection 生命周期中,鉴权过程位于第一步。鉴权完成后,连接进入 command phase,client 才能继续向 server 发送 command。本文聚焦 client 连接时 user/password 的认证过程。

本文内容基于 MySQL Community 8.0.33。

鉴权过程概览

MySQL 支持多种鉴权插件,常见有 native_password_authenticatecaching_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
      }

连接鉴权交互

  1. Server 发送 initial Handshake(包含 scramble 与 plugin 信息)
  2. Client 基于 plugin 对 scramble 处理后返回 Handshake Response
  3. 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。

HandshakeV10

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。

HandshakeResponse41

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

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。

AuthSwitchResponse

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。

参考