MySQL 8 caching_sha2_password 认证迁移实战:老客户端连不上的根因与三种安全迁移方案

升级到 MySQL 8 之后,为什么老客户端连不上了

这几乎是每个从 MySQL 5.7 升级到 8.0 的人都会踩的坑:数据库明明升级成功了,服务也正常启动,但一用老的客户端、老的监控脚本、或者某个用了很久的 PHP 程序去连,就报一个让人一头雾水的错:

ERROR 2059 (HY000): Authentication plugin 'caching_sha2_password' cannot be loaded:
/usr/lib/mysql/plugin/caching_sha2_password.so: cannot open shared object file

或者干脆报 Authentication plugin 'mysql_native_password' cannot be loaded。更迷惑的是:用命令行新装的 mysql 客户端能连,用程序却不行;用程序能连,换个老版本工具又不行。这背后的真凶,就是 MySQL 8 更换了默认的认证插件。

这篇文章把这件事从根上讲清楚:caching_sha2_password 是怎么来的、为什么会导致连不上、怎么安全地迁移、以及为什么「改回 mysql_native_password」是个需要谨慎对待的临时方案。配一套可以一步步照做的排错和迁移流程。

一、MySQL 8 到底改了什么:默认认证插件的换代

在 MySQL 5.7 及更早版本里,用户密码的默认认证方式是 mysql_native_password。它的核心是 SHA-1 哈希,客户端和服务器之间通过一套挑战-应答(challenge-response)机制验证密码,密码本身不通过网络明文传输。这套机制用了几十年,兼容性极好,几乎所有语言的 MySQL 驱动都实现了它。

但 SHA-1 在今天已经不安全了——研究表明可以在可接受的成本下构造 SHA-1 碰撞。出于安全考虑,MySQL 8.0 把默认认证插件换成了 caching_sha2_password,它基于 SHA-256,强度大幅提升,并且引入了缓存机制来弥补强哈希带来的性能开销。

问题就出在这个「更换默认值」上。MySQL 8 安装后,新建的用户默认使用 caching_sha2_password。而老版本的客户端库(尤其是早于 8.0 的那批驱动)根本不认识这个插件——它们不知道怎么做 SHA-256 的挑战应答,也不知道怎么在连接时协商。于是就有了开头那两类报错。

二、caching_sha2_password 的工作机制:为什么「第一次连接慢,之后快」

理解这个插件的工作流程,是理解所有现象的关键。它的认证分两种路径:

  • 快速路径(缓存命中):用户第一次成功认证后,服务器会把密码的 SHA-256 摘要缓存起来。之后的连接,服务器通过缓存直接验证,不走完整流程,速度很快。这是「caching」这个词的由来。
  • 完整路径(缓存未命中):首次认证、服务器刚重启、或者 FLUSH PRIVILEGES 清空缓存后,需要走完整的 RSA 加密流程。

完整路径这里有个关键分支:要分「加密连接」和「非加密连接」两种情况。

  • 如果客户端和服务器之间是 TLS 加密连接:密码可以通过加密通道直接发送,安全。
  • 如果是 明文连接(没开 TLS):caching_sha2_password 要求用服务器公钥加密密码后再发。这需要客户端拿到服务器的 RSA 公钥。

而在明文连接下,客户端拿公钥的方式有两种,靠一个参数控制:--get-server-public-key。如果没指定这个参数,客户端会拒绝继续认证,报错退出——这又是另一个常见的「连不上」原因。

把这两点合起来看,你就能解释清楚那个经典现象了:同一个账号,命令行客户端能连是因为它默认带了 --get-server-public-key;而你的老程序连不上,是因为它的驱动不支持这个参数或者根本不认识 SHA-256 流程。

三、先诊断:查清楚现状再动手

动手改之前,先把当前状态摸清楚。连接上 MySQL 后执行:

-- 看默认认证插件是什么
SELECT @@default_authentication_plugin;

-- MySQL 8.0.27+ 改用这个变量名
SELECT @@authentication_policy;

-- 列出所有用户的认证方式
SELECT user, host, plugin
FROM mysql.user
ORDER BY plugin, user;

典型输出里,你会看到一批用户是 caching_sha2_password,可能还有一些是 mysql_native_password(比如从 5.7 升级上来的老用户,或者你手动改过的)。注意 auth_socket 是 Linux 上 root 用本地 socket 免密登录的方式,不要乱动它,那是故意设计的。

然后确认你的客户端到底支不支持新插件:

-- 查看客户端版本和能力
SELECT VERSION();
SHOW PLUGINS;

客户端这边,最直接的是看 mysql 命令行工具的版本,以及你的应用用的驱动版本(PHP 的 mysqli/PDO_MySQL、Python 的 PyMySQL/mysqlclient 等)。记住一个基线:所有客户端/驱动都应该升级到 8.0 或更新的版本,才能真正原生支持 caching_sha2_password。

四、迁移方案一:升级客户端(首选,治本)

最正确的做法是升级客户端而不是降级服务器。安全方向永远应该往前看。具体到不同语言:

  • PHP:使用 PHP 7.4+ 并确保 mysqlnd 驱动启用。PHP 7.4 及以上的 mysqlnd 原生支持 caching_sha2_password。PDO_MySQL 从 PHP 7.4 起也支持。如果你还在用 PHP 5.x 或者老版本的 libmysqlclient 编译的扩展,那就必须先升级 PHP。
  • Python:mysqlclient 需要 1.4.x 以上版本并链接 MariaDB Connector 或 MySQL 8 的库;PyMySQL 需要 0.9.x 以上;mysql-connector-python 从 8.0 起原生支持。
  • Java:mysql-connector-java 需要 8.0.x,5.1.x 系列不支持。
  • 命令行工具:升级 mysql-client 到 8.0。

升级完成后,连接时如果走明文连接,要记得加 --get-server-public-key(命令行)或在连接串里配 getServerPublicKey=true(JDBC)。更推荐的做法是直接启用 TLS 连接,让完整路径走加密通道,避免公钥获取的麻烦。

# 命令行连接时探测
mysql -u appuser -p -h db.example.com --get-server-public-key

# 或者强制走 SSL
mysql -u appuser -p -h db.example.com --ssl-mode=REQUIRED

五、迁移方案二:逐个用户切换认证方式

如果客户端短期内升不了级(比如老系统硬依赖某个旧版本的库),可以针对性地把某些用户改回 mysql_native_password,而保持数据库默认还是 caching_sha2_password。这样既兼容了老客户端,又不用把整台服务器降级。

-- 针对单个用户切换(MySQL 8.0 推荐写法)
ALTER USER 'appuser'@'10.0.0.%'
IDENTIFIED WITH mysql_native_password BY '你的强密码';

-- 查看确认
SELECT user, host, plugin FROM mysql.user WHERE user='appuser';

-- 让权限缓存立即生效(一般 ALTER USER 会自动生效,这里保险起见)
FLUSH PRIVILEGES;

有几个重要的注意点:

  • 语法随版本变化。MySQL 8.0.14 及以后,GRANT ... IDENTIFIED BY 不再用于改密码,必须用 ALTER USER。IDENTIFIED WITH ... BY 是正确写法。
  • 改的是「该用户以后用什么插件认证」,不是改密码哈希算法回退。密码本身会按新插件重新计算存储,所以老密码仍能用(前提是你写的是同一个密码)。
  • 不要全局降级默认插件。把 default_authentication_plugin 改回 mysql_native_password 是很多人图省事的做法,但这等于把整台服务器的安全基线拉回到 SHA-1,新建的所有用户都会用弱插件。仅在过渡期内、且明确知道风险时才用,并且要记录待办跟进。

六、方案三:明文连接下用 RSA 公钥的正确配法

如果因为环境限制没法上 TLS,又必须用 caching_sha2_password,那就得走 RSA 公钥路线。这里的原则是宁可让客户端每次都向服务器要公钥,也不要把公钥硬编码在客户端。

服务器端确认公钥文件存在:

SHOW VARIABLES LIKE 'caching_sha2_password_public_key_path';
SHOW VARIABLES LIKE 'caching_sha2_password_auto_generate_rsa_keys';

-- 手动获取服务器公钥(客户端侧执行)
-- 或在应用连接串里设置 getServerPublicKey=true

对 PHP 的 PDO 来说,可以这样连接(伪代码示意):

$pdo = new PDO(
    "mysql:host=db.example.com;dbname=mydb",
    "appuser", "password",
    [PDO::MYSQL_ATTR_SSL_CA => "/etc/ssl/certs/ca.pem"]  // 推荐:直接上 TLS
);

更省心的做法还是干脆把连接升级成 TLS。MySQL 8 默认就会生成自签名证书,把服务端 CA 拷给客户端验证即可,这样 caching_sha2_password 的完整路径就自动走加密通道了,不需要处理 getServerPublicKey 这个参数。

七、验证与排查清单

改完之后,用这套清单逐项确认:

  • 服务器侧:SELECT user, host, plugin FROM mysql.user; 确认目标用户的插件是你期望的。
  • 客户端侧:用实际应用去连(不要只用命令行客户端),因为命令行默认行为和驱动的默认行为经常不一样。
  • 明文连接场景:故意用不带 --get-server-public-key 的明文连接试一次,如果报错,说明这个路径需要显式处理,要确保应用连接串配置正确。
  • 连接池:应用如果用了连接池,改认证插件后要重启应用让连接池丢弃旧连接,否则池里可能残留用旧插件建立的连接,行为不一致。
  • 监控脚本:别忘了那些不起眼的定时脚本、备份脚本、Prometheus 的 mysqld_exporter,它们用的也是独立的客户端库,同样需要检查。

八、给个人站长的实操建议

  • 升级 MySQL 前先盘客户端。在动手升级之前,列出所有连数据库的东西:应用的驱动版本、命令行工具、备份脚本、监控 exporter、连上去的 GUI 工具。提前确认它们是否支持 caching_sha2_password。
  • 优先升级客户端,而不是降级服务器。这是唯一不牺牲安全性的路线。老组件如果实在动不了,再针对性地给个别用户开 mysql_native_password 作为过渡。
  • 能上 TLS 就上 TLS。MySQL 8 自带证书生成,启用 TLS 不但让缓存认证的完整路径更顺,也顺带保护了数据库流量。这是性价比很高的一步。
  • 把「降级默认插件」当技术债记账。如果迫不得已改了 default_authentication_plugin,就在运维笔记里记一笔,排个期把客户端升级到位后改回来。
  • 改完必须实测应用连接。不要只看命令行能连就以为万事大吉。用真实应用的连接路径验证,才是唯一可信的标准。

认证插件换代这件事,本质上是一次安全与兼容性的取舍。MySQL 8 选择往前迈了一步,代价就是逼着我们把生态里的旧组件推着一起升级。理解 caching_sha2_password 的两条认证路径和它的缓存机制,你就能在遇到各种「连不上」的报错时,不再靠猜,而是顺着流程一层层定位到真正的原因。这比记住一个「改回 mysql_native_password」的偏方,有价值得多。

Last modification:October 14th, 2026 at 08:25 pm

Leave a Comment