ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

MySQL 8.0连接报错?认证插件不兼容的排查与修复指南

MySQL 8.0连接报错?认证插件不兼容的排查与修复指南 如果你最近正好在折腾 MySQL 8.0尤其是从 5.7 往上迁移或者干脆用 Docker 拉了一个mysql:8.0镜像准备跑业务大概率会在连接阶段撞上一面墙——报错信息是一串英文Client does not support authentication protocol requested by server; consider upgrading MySQL client。这个报错我在不同的环境里遇到不下五次每次都是连接工具或驱动版本没跟上服务器认证方式的升级。很多朋友第一反应是去改密码或者重装 MySQL但真正的原因并不是密码错了而是认证插件不匹配。这篇文章就把这个报错发生的原因、排查思路和所有可行的解决办法一次性说透适合正在迁移 MySQL 8.0 的开发者、运维以及用 Docker 部署 MySQL 后卡在连接环节的新手。1. 先搞清楚报错到底在骂谁认证插件不兼容1.1 把报错翻译成人话报错原文拆开看并不复杂Client does not support...客户端连接工具、驱动、命令行客户端不支持服务器要求的认证协议authentication protocol requested by server服务器要求的是某一种认证协议consider upgrading MySQL client建议升级 MySQL 客户端。本质上这是 MySQL 在握手阶段就拒绝了你。服务端说“我要用caching_sha2_password这种认证方式和你对话”客户端这边回了一句“我不会这个”。于是连接直接被掐断密码还没来得及校验所以你会看到“Access denied”之前的这个协议报错。有些工具显示方式不同比如报Authentication plugin caching_sha2_password cannot be loaded或者是Public Key Retrieval is not allowed这些都是同一个大问题在不同客户端上的不同表现。只要看到“authentication protocol”“authentication plugin”“public key retrieval”这些关键词基本都能归到一类。1.2 为什么 MySQL 8.0 要换认证方式MySQL 5.7 及更早版本默认用的是mysql_native_password认证插件。这个插件在当年够用但实现方式比较简单客户端和服务端基于 SHA1 做一个挑战-响应验证服务器上存储的也是密码的 SHA1 哈希。问题在于这种哈希不能加盐一旦攻击者拿到mysql.user系统表的数据就可以离线暴力破解密码。MySQL 8.0 把默认认证插件换成了caching_sha2_password。这个名字看着长核心变化有两个密码哈希基于 SHA256安全性高很多首次认证时要么走 TLS 加密通道要么通过 RSA 公钥把密码加密后再发给服务器避免密码在网络传输中裸奔。同时它有一个缓存机制某个用户第一次完成认证后服务端会把认证结果缓存起来后续短时间内的连接就不需要重复走完整的公钥加密流程对性能影响很小。打个比方服务器换了一把新锁你还拿着旧钥匙去开当然开不了。不是密码的问题是你们双方约定的“开锁协议”根本没对上。1.3 最容易触发这个报错的场景根据我的经验以下场景最容易撞上老版本 GUI 工具连新库Navicat 12 及更早、SQLyog 老版本这类工具对 MySQL 8.0 的新认证支持都很差历史项目依赖太旧Java 项目里躺着mysql-connector-java 5.xPython 项目用老版本 PyMySQLDocker 部署后本地连接mysql:8.0镜像初始化时root 用户默认用的就是caching_sha2_password如果你本机连接工具是老版本立刻报错公司内部封闭环境工具和驱动都由统一分发版本常年不升级碰到 MySQL 8.0 集体“阵亡”。2. 动手之前先摸清现场三步定位问题来源2.1 确认服务器端版本和用户认证方式出现报错后先不要急着改配置。第一步是确认服务器端当前用户到底要求什么认证插件。如果命令行下还能连上比如用服务器本地的 mysql 客户端或者你本来就有另一个能连上的账号执行SELECT VERSION(); SELECT user, host, plugin, password_expired, account_locked FROM mysql.user WHERE user root;重点看plugin列。如果是caching_sha2_password那就证实了服务端要求新协议如果是mysql_native_password说明服务端本身还允许老协议那问题可能出在别的地方。顺手再查一下全局默认配置SHOW VARIABLES LIKE default_authentication_plugin;注意MySQL 8.4 之后这个变量已经被移除了8.0 系列还能查到。如果你用的是 8.4要看的是authentication_policy变量。2.2 确认客户端和驱动的版本这一步不能省。我习惯把连接链路里每一环都列成一张表填完之后问题基本就锁定了对象版本是否支持默认认证MySQL Server8.0.x是它自己就是新协议mysql 命令行客户端与服务器同版本是Navicat12.x否JDBC 驱动5.1.x否PyMySQL0.9需要配合 cryptography 包PHP mysqlnd7.2.4 以下否每一栏填完你会看到是哪一环卡住了。大多数情况下报错都是“服务器是新的客户端工具或驱动是老的”这个组合。2.3 决定改服务器还是改客户端搞清楚“谁不支持谁”之后就到了决策环节。我的判断标准是这样的如果客户端工具是个人电脑上安装的优先升级工具这是最省事也最安全的路如果客户端驱动是历史项目里带的先评估升级成本升级不了再考虑改服务器端如果数据库是生产环境不建议为了兼容老工具而全局降级认证插件如果只是临时排查可以把某个用户单独改回老插件但要在工单里记一笔后续安排升级。提示能改客户端就不要先改服务器。把数据库认证方式整体降级所有新建用户都会默认走老协议后续安全审计会比较被动。3. 最直接的解法把用户认证方式调回 mysql_native_password3.1 对已有用户单独修改如果客户端一时半会儿升不了级最直接的办法就是把用户认证插件单独改回mysql_native_password。登录 MySQL 后执行ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 你的新密码; ALTER USER root% IDENTIFIED WITH mysql_native_password BY 你的新密码; FLUSH PRIVILEGES;这里有几个细节要特别提醒localhost和%不要漏。之前有人只改了localhost远程连接还是报错因为他的应用连的账号匹配的是%那条记录这条命令在改插件的同时也重设了密码所以执行完要记得同步修改应用里的数据库账号密码FLUSH PRIVILEGES在多数场景下不是必须的ALTER USER已经生效但执行一下不会有害尤其当你用了--skip-grant-tables之类的方式恢复过权限表时。改完再确认一次SELECT user, host, plugin FROM mysql.user WHERE user root;看到plugin变为mysql_native_password再用原来的工具重连基本就通了。3.2 新建用户时直接指定插件如果你在短时间内没法升级客户端又需要为某个应用创建免密或固定密码的账号可以让新用户从创建那一刻就直接使用老插件CREATE USER appuser% IDENTIFIED WITH mysql_native_password BY apppass; GRANT SELECT, INSERT, UPDATE, DELETE ON mydb.* TO appuser%;这样新建用户默认不受caching_sha2_password影响应用侧什么都不用改。3.3 全局兜底修改默认认证插件含 Docker 场景如果希望整个实例的新建用户都默认走mysql_native_password可以修改配置文件[mysqld] default_authentication_pluginmysql_native_password保存后重启 MySQL 服务生效。改之前记得备份原配置文件。如果你是 Docker 部署的 MySQL操作路径稍有不同。比如启动容器时把配置目录挂载出来docker run -d --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDYourPassword \ -v /opt/mysql/conf.d:/etc/mysql/conf.d \ mysql:8.0宿主机上新建/opt/mysql/conf.d/my.cnf写上[mysqld] default_authentication_pluginmysql_native_password然后重启容器docker restart mysql8如果要修改已有用户的插件直接用docker exec进入容器执行 SQLdocker exec -it mysql8 mysql -uroot -p注意Docker 里通过MYSQL_ROOT_PASSWORD环境变量初始化的 root 用户默认认证插件同样是caching_sha2_password不存在“容器里就是老协议”的例外所以该改还是要改。4. 不想动服务器认证那就把客户端升级到位4.1 GUI 工具与命令行客户端最省心的办法其实是升级客户端而不是去迁就服务器。Navicat 15 及之后版本对 MySQL 8.0 的新认证支持比较好12 及更早版本就经常撞协议报错MySQL Workbench 8.0 系列本身没这个问题HeidiSQL、DBeaver 近年版本也基本兼容。升级工具有个容易忽略的操作升级后要把原来的连接配置删掉重建因为工具内部会缓存认证插件相关的信息不清理的话可能继续报错。临时验证可以用服务器自带的命令行客户端mysql -h 127.0.0.1 -P 3306 -uroot -p命令行走通说明服务端没问题问题只在具体工具或驱动上。4.2 Java/JDBC重点在连接串参数Java 项目连 MySQL 8.0 有两个常见坑。第一个是驱动版本太老先升级mysql-connector-java到 8.x。第二个是升级之后可能还会报Public Key Retrieval is not allowed这是因为caching_sha2_password在非 TLS 连接下需要客户端向服务器请求 RSA 公钥然后客户端用公钥把密码加密再发给服务器。出于安全考虑JDBC 驱动默认不允许自动获取公钥必须显式开启。连接串里加上这两个参数jdbc:mysql://127.0.0.1:3306/mydb?useSSLfalseallowPublicKeyRetrievaltrueserverTimezoneAsia/Shanghai简单解释一下allowPublicKeyRetrievaltrue允许客户端向服务器获取 RSA 公钥解决公钥获取限制的问题useSSLfalse因为如果你不开 SSL就走公钥交换流程如果连接本身已经配置了 SSL 证书这条可以不加但公钥参数还是要酌情保留serverTimezoneMySQL 8.0 对时区处理比 5.x 严格很多老项目在升级时都会顺带遇到时区转换报错加上这个参数能少踩一个坑。4.3 Python/PHP/Go 等语言驱动的匹配版本语言驱动的兼容性可以按下面这个表快速核对语言/驱动兼容版本提示额外注意事项PyMySQL1.0 以上版本稳定连接报 RSA 相关错误时执行pip install cryptographymysql-connector-python8.x 原生支持连接参数里可以显式指定auth_pluginPHP mysqlndPHP 7.2.4老 PHP 项目升级要连带评估应用兼容性Go go-sql-driver/mysqlv1.5新版驱动一般没问题MySQL ODBCODBC 8.0需要配好 Microsoft Visual C 2015-2022 运行库升级驱动之后一定要重启应用清空连接池里的旧连接否则应用可能还在用启动时加载的旧驱动类表现上就是“升级了但还是报错”。5. 实测复盘一次完整的排错链路5.1 场景还原我拿一个很典型的场景走一遍完整流程Windows 开发机上用 Navicat 12 连接 Docker 里的mysql:8.0容器端口映射为 3306。第一步Navicat 连接报Client does not support authentication protocol requested by server。先不搜答案先用命令行确认服务端是活的mysql -h 127.0.0.1 -P 3306 -uroot -p命令行走通说明容器正常问题出在认证协议层。第二步进入容器查用户认证插件docker exec -it mysql8 mysql -uroot -pSELECT user, host, plugin FROM mysql.user WHERE user root;结果里 root 的记录是caching_sha2_password。第三步判断方向。Navicat 12 是公司统一安装的老版本没法立刻升级那就临时改用户插件ALTER USER root% IDENTIFIED WITH mysql_native_password BY NewPass123!; FLUSH PRIVILEGES;第四步Navicat 重新连接成功。这里有一个很关键的判断连接走的是哪条 host 记录。查询结果里如果存在root%远程连接基本都会匹配到它所以只改localhost是没用的。5.2 不同报错形态的快速对照不同客户端对同一个底层问题的表达方式不一样整理成表方便对照报错原文/关键词根因解法方向Client does not support authentication protocol requested by server客户端不认 caching_sha2_password升级客户端或改用户插件为 nativeAuthentication plugin caching_sha2_password cannot be loaded驱动版本太老加载不了新插件升级驱动Public Key Retrieval is not allowedJDBC 在非 TLS 下没开公钥获取连接串加 allowPublicKeyRetrievaltrueAccess denied for user roothost (using password: YES)密码错误或 host 匹配错误核对账号 host 和密码必要时 ALTER USER 重设连接后提示密码过期password_expired 为 Y重设密码或设置 PASSWORD EXPIRE NEVER5.3 改完连接仍失败的三个隐藏坑第一种坑是连接池缓存。应用启动时建立的连接还在用旧握手状态服务端那边可能检测到协议不匹配直接断开表现为“改了插件还是间歇性报错”。处理方式是重启应用或者清空连接池。第二种坑是 host 记录没改对。判断自己当前到底走了哪条 host 记录用这条 SQLSELECT CURRENT_USER();比如结果是root%那你要改的就不是rootlocalhost而是root%那条。第三种坑是密码策略限制。改插件并重设密码时如果开了validate_password组件新密码必须满足长度、大小写、数字、特殊字符的要求。测试环境可以临时放宽策略但生产环境不建议这么做。6. 别只盯插件版本趋势与长期规划6.1 MySQL 8.4 之后mysql_native_password 开始退场8.0 系列还能靠default_authentication_pluginmysql_native_password全局兜底但从 MySQL 8.4 开始官方在默认配置里禁用了mysql_native_password旧参数也被authentication_policy取代。如果确实还想启用老插件需要显式开启mysql_native_passwordON。这说明官方态度已经很明确所有客户端最终都要迁到caching_sha2_password。所以这次通过改回 native 插件解决问题只能算“救急”不是长期方案。有条件的话还是把客户端驱动升级排在后续工作计划里。MySQL 8.4 里查看当前认证策略SHOW VARIABLES LIKE authentication_policy;6.2 生产环境的认证兼容改造建议最后给生产环境一个可落地的改造思路这是我实际处理过多次迁移后总结出来的顺序先做一次认证插件盘点SELECT user, host, plugin, COUNT(*) FROM mysql.user GROUP BY plugin;然后按业务优先级逐个迁移用户插件。每改完一个用户立刻用对应业务链路的测试脚本连接一次。整个过程建议放在低峰期避免影响线上。有一点我要特别强调不要为了兼容一个老工具就让整个生产库全局降级认证插件。这个决定当时看着省事后续安全评审时会被反复追问。正确做法是“单用户兼容、整体升级客户端”。另外不管用哪种认证方式都不要把明文密码留在配置文件或 Docker 环境变量里。Docker Compose 可以配合env_file或 secret 机制管理密码这和本次报错没有直接关系但同类排错时很容易顺手踩到密码泄露风险值得一起改掉。这个问题我前前后后处理过很多次。早期图省事上来就改回mysql_native_password后来在安全评审时被问得难受。现在的习惯是先查客户端版本能升级就升级只有在老工具实在不能动时才去单独改某个用户的认证插件。最后再分享一个小技巧在测试库里同时建一个 native 用户和一个caching_sha2_password用户新工具连新的、老工具连旧的以后排查类似的协议问题能节省大量来回确认的时间。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进