MySQL 8.4:未使用 mysql_native_password 却报插件未加载(Plugin 'mysql_native_password' is not loaded)?
<h1 data-tool="mdnice编辑器"><span style="color: rgba(0, 128, 0, 1)">现象</span></h1><p data-tool="mdnice编辑器"><span>最近遇到一个有趣的案例:在一个新创建的 MySQL 8.4 实例中,使用用户 u2 登录时,返回了<code><span>Plugin 'mysql_native_password' is not loaded</span></code><span>错误。</span></span></p>
<pre data-tool="mdnice编辑器"><span data-cacheurl="" data-remoteid="" data-lazy-bgimg="https://mmbiz.qpic.cn/mmbiz_svg/BO1qQiajiacVlibSwibjXiaMnypjz5IkC0PQUxK5yQMmngHYqbm3YNdhl8TS8ac7p3YLWsXy3rac60AFicaMdSFlzFHibGyJ0GgBUdg/640?wx_fmt=svg&from=appmsg" data-fail="0"><code><span>$ mysql -h127.0.0.1 -P3316 -uu2 -p123<span><br><span>mysql: Using a password on the command<span> line interface can be insecure.<span><br><span>ERROR 1524 (HY000): Plugin 'mysql_native_password'<span> is not loaded<span><br></span></span></span></span></span></span></span></span></code></span></pre>
<p data-tool="mdnice编辑器"><span>奇怪的是,检查<code><span>mysql.user</span></code><span>表后却发现:</span></span></p>
<ul class="list-paddingleft-1">
<li><span>实例里并没有 u2 这个用户;</span></li>
<li><span>现有用户中并没有用户在使用 mysql_native_password。</span></li>
</ul>
<pre data-tool="mdnice编辑器"><span data-cacheurl="" data-remoteid="" data-lazy-bgimg="https://mmbiz.qpic.cn/mmbiz_svg/BO1qQiajiacVlibSwibjXiaMnypjz5IkC0PQUxK5yQMmngHYqbm3YNdhl8TS8ac7p3YLWsXy3rac60AFicaMdSFlzFHibGyJ0GgBUdg/640?wx_fmt=svg&from=appmsg" data-fail="0"><code><span>mysql> select<span> host,user<span>,plugin<span> from<span> mysql.user;<span><br><span>+-----------+------------------+-----------------------+<span><br><span>| host | user | plugin |<span><br><span>+-----------+------------------+-----------------------+<span><br><span>| % | root | caching_sha2_password |<span><br><span>| localhost | mysql.infoschema | caching_sha2_password |<span><br><span>| localhost | mysql.session | caching_sha2_password |<span><br><span>| localhost | mysql.sys | caching_sha2_password |<span><br><span>| localhost | root | caching_sha2_password |<span><br><span>+-----------+------------------+-----------------------+<span><br><span>5 rows in set<span> (0.05<span> sec)<span><br></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></span></pre>
<p data-tool="mdnice编辑器"><span>有意思的是,同样是不存在,如果使用 u1 登录,返回的却是<code><span>Access denied for user 'xxx'@'xxx'</span></code><span>错误:</span></span></p>
<pre data-tool="mdnice编辑器"><span data-cacheurl="" data-remoteid="" data-lazy-bgimg="https://mmbiz.qpic.cn/mmbiz_svg/BO1qQiajiacVlibSwibjXiaMnypjz5IkC0PQUxK5yQMmngHYqbm3YNdhl8TS8ac7p3YLWsXy3rac60AFicaMdSFlzFHibGyJ0GgBUdg/640?wx_fmt=svg&from=appmsg" data-fail="0"><code><span>$ mysql -h127.0.0.1 -P3316 -uu1 -p123<span><br><span>mysql: Using a password on the command<span> line interface can be insecure.<span><br><span>ERROR 1045 (28000): Access denied for<span> user 'u1'<span>@'127.0.0.1'<span> (using password: YES)<span><br></span></span></span></span></span></span></span></span></span></span></code></span></pre>
<p data-tool="mdnice编辑器"><span>问题来了:</span></p>
<ol class="list-paddingleft-1">
<li><span>同样不存在,为什么 u1 和 u2 会返回不同的错误?</span></li>
<li><span>明明没有用户在使用 mysql_native_password,为什么 MySQL 会提示<code><span>Plugin 'mysql_native_password' is not loaded</span></code><span>?</span></span></li>
</ol>
<h1 data-tool="mdnice编辑器"><span style="color: rgba(0, 128, 0, 1)">根因分析</span></h1>
<p data-tool="mdnice编辑器"><span>下面结合 MySQL 客户端与服务端的认证流程,来分析上述报错。</span></p>
<p data-tool="mdnice编辑器"><span>一、</span></p>
<p data-tool="mdnice编辑器"><span>客户端向 MySQL 服务端发起连接请求。</span></p>
<p data-tool="mdnice编辑器"><span>二、</span></p>
<p data-tool="mdnice编辑器"><span>服务端收到请求后,会调用<code><span>do_auth_once()</span></code><span>函数对客户端进行身份认证。</span></span></p>
<p data-tool="mdnice编辑器"><span>首次认证时,MySQL 会调用默认的密码认证插件进行认证。</span></p>
<p data-tool="mdnice编辑器"><span>在 MySQL 8.4 之前,默认的密码认证插件由 default_authentication_plugin 参数决定:</span></p>
<ul class="list-paddingleft-1">
<li><span>5.7 默认是 mysql_native_password。</span></li>
<li><span>8.0 改成了 caching_sha2_password。</span></li>
<li><span>到了 8.4,移除了这个参数,默认插件被固定为 caching_sha2_password。</span></li>
</ul>
<p data-tool="mdnice编辑器"><span>所以,在 MySQL 8.4 中,MySQL 会调用 caching_sha2_password 插件向客户端发送一个握手包(handshake packet)。</span></p>
<p data-tool="mdnice编辑器"><span>握手包的内容包括通信协议版本、服务端版本、随机数(盐值)、服务端能力标志、默认字符集编号、密码认证插件名称等。</span></p>
<p data-tool="mdnice编辑器"><span>三、</span></p>
<p data-tool="mdnice编辑器"><span>客户端收到握手包后,默认会根据包中指定的认证插件(在 MySQL 8.4 中是 caching_sha2_password)生成并返回一个握手响应包(handshake response)。</span></p>
<p data-tool="mdnice编辑器"><span>响应包的内容包括客户端能力标志、用户名、加密后的密码、要连接的库名、客户端使用的认证插件等。</span></p>
<p data-tool="mdnice编辑器"><span>四、</span></p>
<p data-tool="mdnice编辑器"><span>服务端收到响应包后,会调用<code><span>parse_client_handshake_packet()</span></code><span>函数进行处理。该函数主要做:</span></span></p>
<ol class="list-paddingleft-1">
<li>
<p><span>读取客户端能力标志。</span></p>
</li>
<li>
<p><span>如果客户端要求 SSL,则先完成 SSL 握手并重新读取一个包。</span></p>
</li>
<li>
<p><span>设置客户端使用的字符集。</span></p>
</li>
<li>
<p><span>提取用户名、密码、默认库名和认证插件。</span></p>
</li>
<li>
<p><span>调用<code><span>find_mpvio_user</span></code><span>初始化 mpvio( mpvio 用于存储认证过程中的用户信息、连接信息及插件交互状态)。</span></span></p>
</li>
</ol>
<p data-tool="mdnice编辑器"><code><span>find_mpvio_user</span></code><span>会根据客户端发来的用户名与 host/ip,从 <span>ACL 用户缓存(<code><span>mysql.user</span></code><span>)中找到对应的用户记录。如果用户不存在,MySQL 不会直接暴露“用户名不存在”,而是走 <code><span>decoy_user()</span></code><span> 逻辑,为这类“未知用户”随机分配一个认证插件,构造一个看起来正常的用户记录。这样可以避免外部探测哪些用户名真实存在。</span></span></span></span></p>
<p data-tool="mdnice编辑器"><span>以下是 <code><span>decoy_user()</span></code><span> 函数的具体实现。</span></span></p>
<pre data-tool="mdnice编辑器"><span data-cacheurl="" data-remoteid="" data-lazy-bgimg="https://mmbiz.qpic.cn/mmbiz_svg/BO1qQiajiacVlibSwibjXiaMnypjz5IkC0PQUxK5yQMmngHYqbm3YNdhl8TS8ac7p3YLWsXy3rac60AFicaMdSFlzFHibGyJ0GgBUdg/640?wx_fmt=svg&from=appmsg" data-fail="0"><code><span>ACL_USER *<span>decoy_user<span>(<span>const<span> LEX_CSTRING &username, <span>const<span> LEX_CSTRING &hostname,<span><br><span> MEM_ROOT *mem, struct rand_struct *rand,<span><br><span> <span>bool<span> is_initialized)<span> <span>{<span><br><span> ...<span><br>if<span> (is_initialized) {<span><br><span> // 根据用户名和 host/ip 生成一个 key<span><br><span> <span>Auth_id <span>key<span>(user)<span>;<span><br><span> <span><br><span> uint value;<span><br><span> // 如果该 unknown user 已经出现过,则复用之前分配的认证插件。<span><br><span> // 这样可以保证同一个客户端每次收到的登录验证行为一致,避免泄露用户名是否存在的信息。<span><br><span> if<span> (unknown_accounts->find(key, value)) {<span><br><span> user->plugin = Cached_authentication_plugins::cached_plugins_names;<span><br><span> } else<span> {<span><br><span> // 对于首次遇到的 unknown user,会从 cached_plugins_names 中随机分配一个认证插件。<span><br><span> constint<span> DECIMAL_SHIFT = 1000<span>;<span><br><span> constint<span> random_number = static_cast<span><int<span>>(my_rnd(rand) * DECIMAL_SHIFT);<span><br><span> uint plugin_num = (uint)(random_number % ((uint)PLUGIN_LAST));<span><br><span> user->plugin =<span><br><span> Cached_authentication_plugins::cached_plugins_names;<span><br><span> unknown_accounts->clear_if_greater(MAX_UNKNOWN_ACCOUNTS);<span><br><span> // 将客户端及分配的插件记录到 unknown_accounts 缓存中<span><br><span> if<span> (!unknown_accounts->insert(key, plugin_num)) {<span><br><span> if<span> (!unknown_accounts->find(key, plugin_num))<span><br><span> user->plugin = default_auth_plugin_name;<span><br><span> else<span><br><span> user->plugin =<span><br><span> Cached_authentication_plugins::cached_plugins_names;<span><br><span> }<span><br><span> }<span><br><span> }<span><br><span> ...<span><br>return<span> user;<span><br><span>}<span><br>// cached_plugins_names 的定义<span><br>const<span> LEX_CSTRING Cached_authentication_plugins::cached_plugins_names[(<span><br><span> uint)PLUGIN_LAST] = {{STRING_WITH_LEN(<span>"caching_sha2_password"<span>)},<span><br><span> {STRING_WITH_LEN(<span>"mysql_native_password"<span>)},<span><br><span> {STRING_WITH_LEN(<span>"sha256_password"<span>)}};<span><br></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></span></pre>
<p data-tool="mdnice编辑器"><span>对于 u1 用户,随机分配到的认证插件可能是 caching_sha2_password 或 sha256_password,对于 u2 用户,则恰好分配到了 mysql_native_password。</span></p>
<p data-tool="mdnice编辑器"><span>五、</span></p>
<p data-tool="mdnice编辑器"><span>在<code><span>parse_client_handshake_packet()</span></code><span>函数中,如果发现客户端使用的密码认证插件与<code><span>mysql.user</span></code><span>表中记录的插件不一致(对于“伪用户”,则是随机分配的插件),MySQL 会调用<code><span>do_auth_once()</span></code><span>进行二次认证。</span></span></span></span></p>
<p data-tool="mdnice编辑器"><span>此时,认证会使用<code><span>mysql.user</span></code><span>表中的插件,或者伪用户被随机分配的插件。</span></span></p>
<p data-tool="mdnice编辑器"><span>如果指定的认证插件在服务端不存在,则会触发<code><span>Plugin 'xxx' is not loaded</span></code><span>错误。</span></span></p>
<p data-tool="mdnice编辑器"><code><span>do_auth_once()</span></code><span>的具体实现如下:</span></p>
<pre data-tool="mdnice编辑器"><span data-cacheurl="" data-remoteid="" data-lazy-bgimg="https://mmbiz.qpic.cn/mmbiz_svg/BO1qQiajiacVlibSwibjXiaMnypjz5IkC0PQUxK5yQMmngHYqbm3YNdhl8TS8ac7p3YLWsXy3rac60AFicaMdSFlzFHibGyJ0GgBUdg/640?wx_fmt=svg&from=appmsg" data-fail="0"><code><span>static<span> <span>int<span> <span>do_auth_once<span>(THD *thd, <span>const<span> LEX_CSTRING &auth_plugin_name,<span><br><span> MPVIO_EXT *mpvio)<span> <span>{<span><br><span> DBUG_TRACE;<span><br>int<span> res = CR_OK, old_status = MPVIO_EXT::FAILURE;<span><br>bool<span> unlock_plugin = false<span>;<span><br>// 先尝试从缓存中获取指定插件<span><br><span> plugin_ref plugin =<span><br><span> g_cached_authentication_plugins->get_cached_plugin_ref(&auth_plugin_name);<span><br>// 若缓存中不存在,则按名称加载插件<span><br>if<span> (!plugin) {<span><br><span> if<span> ((plugin = my_plugin_lock_by_name(thd, auth_plugin_name,<span><br><span> MYSQL_AUTHENTICATION_PLUGIN)))<span><br><span> unlock_plugin = true<span>;<span><br><span> }<span><br><span><br><span> mpvio->plugin = plugin;<span><br><span> old_status = mpvio->status;<span><br>// 如果插件存在,则调用对应的 authenticate_user() 方法与客户端进行认证交互<span><br>if<span> (plugin) {<span><br><span> st_mysql_auth *auth = (st_mysql_auth *)plugin_decl(plugin)->info;<span><br><span> res = auth->authenticate_user(mpvio, &mpvio->auth_info);<span><br><span><br><span> if<span> (unlock_plugin) plugin_unlock(thd, plugin);<span><br><span> } else<span> {<span><br><span> // 如果插件无法加载,就会触发 Plugin xxx is not loaded 错误。<span><br><span> Host_errors errors;<span><br><span> errors.m_no_auth_plugin = 1<span>;<span><br><span> inc_host_errors(mpvio->ip, &errors);<span><br><span> my_error(ER_PLUGIN_IS_NOT_LOADED, MYF(0<span>), auth_plugin_name.str);<span><br><span> res = CR_ERROR;<span><br><span> }<span><br><span> ...<span><br>return<span> res;<span><br><span>}<span><br></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></span></pre>
<p data-tool="mdnice编辑器"><span>具体到 u2 用户,因为分配的认证插件正好是 mysql_native_password,所以在二次认证阶段,MySQL 会尝试使用该插件进行验证。</span></p>
<p data-tool="mdnice编辑器"><span>但在 MySQL 8.4 中,mysql_native_password 默认是被禁用的,所以就触发了<code><span>ERROR 1524 (HY000): Plugin 'mysql_native_password' is not loaded</span></code><span>错误。</span></span></p>
<h1 data-tool="mdnice编辑器"><span style="color: rgba(0, 128, 0, 1)">总结</span></h1>
<p data-tool="mdnice编辑器"><span>当客户端使用一个不存在的用户名连接 MySQL 时:</span></p>
<ul class="list-paddingleft-1">
<li><span>MySQL 不会直接提示用户不存在</span></li>
<li><span>而是为该用户构造一个“假用户”</span></li>
<li><span>并随机分配一个认证插件进行认证,以防止用户名枚举攻击</span></li>
</ul>
<p data-tool="mdnice编辑器"><span>在 MySQL 8.4 中,由于默认禁用了 mysql_native_password,因此,</span></p>
<ul class="list-paddingleft-1">
<li><span>若随机分配到该插件,就会触发<code><span>ERROR 1524 (HY000): Plugin 'mysql_native_password' is not loaded</span></code><span>错误。</span></span></li>
<li><span>若分配到其他插件,则会走完整认证流程并返回<code><span>Access denied for user 'xxx'@'xxx'</span></code><span>错误。</span></span></li>
</ul>
<p data-tool="mdnice编辑器"><span>以上就是 u1 和 u2 报错不同的根本原因。</span></p><br><br>
来源:https://www.cnblogs.com/ivictor/p/19262263
頁:
[1]