MySQL 8 Public Key Retrieval报错原理与五种解决配置方案 1. 从一个报错说起MySQL 8连接时最常见的一道坎大约在一个月前同事跑来找我说有一个老项目本地连不上数据库了控制台抛出来的错误信息就是那句让人看着眼熟却又说不清机制的Public Key Retrieval is not allowed。我当时第一反应就是又是 MySQL 8.0 默认认证插件在“捣鬼”。这个报错在升级到 MySQL 8 之后相当常见尤其是在用 JDBC、Navicat、DBeaver 等客户端直连时只要你用的不是 SSL 加密连接而用户名对应的认证方式又是caching_sha2_password客户端就会尝试向服务器要 RSA 公钥来加密密码传输可如果客户端这个选项被禁用了那就会直接报出一模一样的错误。其实这个错误本身并不复杂但它涉及的机制值得展开讲讲。它不是什么“神秘的连接异常”也不是你的密码错了、端口不通了而是 MySQL 8.0 之后默认的密码认证策略发生了变化导致客户端和服务端之间在“如何安全地传输密码”这件事上没达成一致。理解了这一点解决方案就自然浮出水面了。这个报错主要出现在三类场景里一是你用 JDBC 连接串直连 MySQL 8二是你用图形化客户端连接时勾选或未勾选某些隐藏选项三是某些自动化部署脚本里连接参数写得不完整。适合遇到这个问题后想彻底搞懂原理而不是只知道“改个参数就完事”的开发者和运维同学阅读。先给你一个最直接的解决办法后面再慢慢拆细节如果你是 JDBC 连接在连接字符串末尾加上allowPublicKeyRetrievaltrue并且把useSSLfalse显式写出来大概率就能解决。如果你用的是命令行客户端就在连接命令里加上--get-server-public-key参数。如果你用的是图形化工具去连接配置里找一下“允许获取服务器公钥”或者allowPublicKeyRetrieval选项勾上。但这只是治标弄懂背后的机制才能真正举一反三遇到变种问题不慌。2. 为什么会有这个报错认证插件与公钥检索机制的原理2.1 MySQL 8 默认认证方式变成了 caching_sha2_password时间线往回捋一下。在 MySQL 5.7 及更早的版本中默认的认证插件是mysql_native_password这种认证方式用的是挑战-应答机制challenge-response本质上是在客户端和服务器之间做一次密码散列比对不需要在网络上传输明文密码而且整个校验过程不需要额外获取密钥。它的好处是简单、兼容性好几乎所有客户端都支持缺点则是安全强度有限因为基于 SHA1 的散列算法在如今看来已经不够安全而且如果有心人拿到了密码散列存在离线破解的风险。所以 MySQL 8.0 把默认认证插件换成了caching_sha2_password它基于 SHA256 做加密比旧的 SHA1 方案强了一个档次。但这个新插件在工作机制上有一个关键变化当客户端和服务端之间没有建立 SSL 加密通道时caching_sha2_password需要先向服务端请求一个 RSA 公钥然后用这把公钥对用户的密码做 RSA 加密再把密文发送给服务端解密并校验。这就像你和对方第一次见面为了安全传达一个秘密口令你需要先把对方给的保险箱锁上再把锁好的箱子送回去。这里的“保险箱”就是公钥。如果客户端没有提前配置好公钥也没有权限去主动向服务端索要那把“公钥”那密码就无法安全传输连接自然就被中断了。于是你就看到了Public Key Retrieval is not allowed这个报错。2.2 allowPublicKeyRetrieval 参数在扮演什么角色在 MySQL 的客户端连接参数中allowPublicKeyRetrieval这个选项控制着客户端是否允许自动向服务端请求 RSA 公钥。常见的传统客户端工具和 JDBC 驱动为了规避中间人攻击的风险默认把这个选项设置为false。也就是说即使服务端提供了公钥客户端也选择“视而不见”拒绝接收。这是一层安全保护但也是一层让人摸不着头脑的屏障。你可以这样理解客户端和服务端之间的沟通很像两个初次合作的人互传密信。如果一切走加密通道SSL那就相当于找了一个信得过的快递员如果没有加密通道客户端得主动向对方索要一把临时公钥来加密内容而索要这个动作本身有可能被中间人利用。于是客户端默认的策略是“我不主动要”。但 MySQL 8 服务端又需要你这么做两边就僵住了报错就这样产生了。所以当你把allowPublicKeyRetrievaltrue打开等于告诉客户端可以主动去索要公钥完成了密码的加密传输。但与此同时你也放弃了一层针对中间人攻击的防护。这也解释了为什么很多安全要求严格的团队会强调不要只开启这个参数而不开启 SSL更好的做法是直接配置 SSL 证书或者把用户认证方式调整成更合适的插件。2.3 触发报错的具体条件组合不是所有 MySQL 8 连接都会遇到这个错。经过我实际测试触发这个报错至少要同时满足几个条件服务端是 MySQL 8.0 及以上版本。用户的认证插件是caching_sha2_password。客户端连接时没有走 SSL 加密通道。客户端连接参数里没有显式配置 RSA 公钥也没有允许自动获取公钥。客户端没有通过其他方式预先拿到公钥并配置到连接里。在这五个条件下报错几乎是必然的。你可以自己检查一下如果连接用的用户名在创建时指定了mysql_native_password插件那大概率不会遇到这个报错因为走的是老式校验流程。如果你已经开启了 SSL那服务端会让密码走加密隧道传输不需要 RSA 公钥介入。明白了这些组合条件你就能对人下药而不是盲目试参数。3. 第一种解法从客户端入手开启公钥检索3.1 命令行客户端的处理方法如果你习惯用 mysql 命令行直连那么解决方式很简单。MySQL 8 的命令行客户端专门提供了一个参数--get-server-public-key使用方式如下mysql -h 127.0.0.1 -P 3306 -u test_user -p --get-server-public-key我实测下来加了这个参数之后连接就不会再报公钥检索不允许的错误了。和这个参数相对的还有另一个叫--server-public-key-path的选项它用于指定一个本地保存的服务端公钥文件路径。区别在于--get-server-public-key是每次连接时动态向服务端索取公钥而--server-public-key-path是直接用本地已有的公钥文件。如果你想把公钥提前拿到本地可以在服务端执行下面的 SQL 查询SHOW STATUS LIKE Caching_sha2_password_rsa_public_key;查询结果就是服务端的 RSA 公钥字符串。你可以把它保存到一个文件中然后在客户端连接时通过--server-public-key-path/path/to/public_key.pem指定。这样做的好处是不需要每次连接都额外走一次公钥获取流程安全性上也比完全放开“自动获取”好一些因为公钥来源是确定的不会随随便便信任服务端返回的任何公钥。3.2 JDBC 连接串的调整方式Java 开发者遇到这个报错的概率应该是所有场景里最高的。原因很简单很多老项目的数据库驱动还是老版本的 MySQL Connector/J而且连接串往往经历了多次复制粘贴缺少新版本的配置项。解决方案是在 JDBC URL 末尾补上两个参数jdbc:mysql://127.0.0.1:3306/test_db?useSSLfalseallowPublicKeyRetrievaltrueuseSSLfalse表示不使用 SSL 连接allowPublicKeyRetrievaltrue允许客户端从服务端获取公钥用于密码加密。两个参数配合起来就是明确的“我不用加密通道但我允许获取公钥”的信号。如果你用的是 HikariCP、Druid 等连接池也可以把这两个参数配置到连接池的 dataSource 属性上效果是一样的。我见过不少人在这一步容易犯一个错误只加了allowPublicKeyRetrievaltrue但useSSL没写然后发现报错变成了 SSL 相关的其他错误或者干脆不生效。因为 MySQL 驱动在useSSL未显式指定时某些版本会尝试自动协商 SSL行为表现不一致容易造成排查时一头雾水。我的建议是如果内网环境不打算用 SSL就两个参数都显式写出来不留歧义。3.3 图形化客户端的勾选与配置日常开发中很多同事习惯用 Navicat、DBeaver、DataGrip 这类图形化客户端。不同工具对这个参数的暴露方式不太一样Navicat在连接属性的“高级”标签页里勾选“使用 SSL”之外还有一个“允许使用公钥检索”之类的选项需要勾选上。不同 Navicat 版本的位置略有差异老版本可能藏在“SSH/SSL”或“高级”分类下。DBeaver在连接配置的“驱动属性”面板中找到allowPublicKeyRetrieval一项把默认的 false 改成 true。DBeaver 的驱动属性展示比较完整也可以直接新增属性。DataGrip在连接配置界面的“高级”标签页搜索allowPublicKeyRetrieval修改为 true同时可以在“SSH/SSL”页把连接设置为不使用 SSL或者使用 SSL 后配合证书配置。如果用的是数据库管理平台的网页端控制台一般不受这个报错影响因为平台方通常已经处理好了驱动参数。这个报错更常出现在你本地开发环境或者自建运维脚本里。实际操作中我建议图形化客户端优先考虑加公钥检索因为改认证插件会影响到所有其他连接者。一次性勾选生效快且不引入服务端变更对团队其他人也是透明的。4. 第二种解法从服务端入手调整用户认证方式4.1 把用户的认证插件改成 mysql_native_password如果你不想在每个客户端都去开启公钥检索也可以从服务端根治问题把出问题用户的认证方式从caching_sha2_password改回mysql_native_password。这个方案在不少老项目里是“救火队长”一样的存在因为改完一了百了各种客户端都不需要再调整参数。操作方式很直接用管理员账号执行ALTER USER test_user% IDENTIFIED WITH mysql_native_password BY 你的密码;执行完再刷新一下权限FLUSH PRIVILEGES;刷新之后再用原来的方式连接报错就消失了。这个方案我一般在两种场景下用得很果断一是老项目短期内没有升级计划团队成员用的客户端五花八门统一改服务端最省心二是项目里有历史代码很多地方写死了 JDBC 连接串不方便逐个改参数。需要提醒的是mysql_native_password在 MySQL 8.0 里仍然是可用的只是不再作为默认插件。官方后续版本比如 MySQL 9已经开始把mysql_native_password标记为废弃所以这个方案只能算过渡方案不建议在新项目里主动选择它。如果你的项目还在用 MySQL 5.7 时代的代码短期内没问题但长期规划里还是应该留出迁移时间窗口。4.2 重新创建用户并显式指定认证插件有时候用户已经存在且被很多系统引用直接改密码或认证插件可能会有一些顾虑。这种情况下可以创建一个新用户并且在创建时就显式指定认证插件避免后续各种踩坑CREATE USER new_user% IDENTIFIED WITH caching_sha2_password BY 密码;如果你希望新用户走老式认证只需要把caching_sha2_password换成mysql_native_password即可。这里有一个实操中的小细节IDENTIFIED WITH指定的插件名必须和系统里存在的插件名一致否则会报错提示找不到插件。可以用下面的 SQL 查看当前实例支持的插件SELECT PLUGIN_NAME, PLUGIN_STATUS FROM INFORMATION_SCHEMA.PLUGINS WHERE PLUGIN_NAME LIKE %password%;正常情况下你会在结果里同时看到caching_sha2_password和mysql_native_password。如果出于安全考虑服务端已经卸载了老插件那就不要硬去指定老老实实走caching_sha2_password方案比较好。4.3 更彻底的服务端配置强制所有新建用户使用指定插件如果你负责搭建新的测试环境或者开发库希望从源头上避免团队里有人再碰到公钥检索报错可以在 MySQL 配置文件my.cnf 或 my.ini里的[mysqld]段添加default_authentication_pluginmysql_native_password添加后重启 MySQL 服务后续CREATE USER而不显式指定插件时就会默认使用mysql_native_password。不过这个参数在 MySQL 8.0 中虽然还能用但会输出 deprecation 警告信息官方推荐的方式依然是使用默认的caching_sha2_password同时在客户端配合allowPublicKeyRetrieval。做这个操作之前建议你先和团队商量好到底是统一走默认的caching_sha2_password配合客户端调整参数还是为了省事直接退回老插件。两个方向没有绝对的对错主要看团队的客户端兼容情况、安全基线要求以及未来的版本升级规划。如果让我给建议新项目一律走默认插件老项目按实际情况取舍。5. 不同客户端场景下的配置速查与对比5.1 各类型客户端的参数配置参考表为了方便快速定位我把常见场景下的配置方式整理成了一张表遇到问题直接对照查找连接场景关键配置适用说明mysql 命令行添加--get-server-public-key参数临时连接、脚本连接均可JDBC 连接串useSSLfalseallowPublicKeyRetrievaltrueJava 项目最常见解决方案Navicat连接属性中勾选“允许获取服务器公钥”不同版本位置有差异DBeaver驱动属性中设置allowPublicKeyRetrievaltrue属性面板中直接改DataGrip高级标签页搜索allowPublicKeyRetrieval并改为 true同样需要显式设置Python PyMySQL连接参数加public_key_retrievalTruePyMySQL 支持该参数Node.js mysql2connection.query前在配置中设authPlugins或加连接参数不同库的配置键名不同Python 这边我单独提一句PyMySQL 的使用方式如下import pymysql conn pymysql.connect( host127.0.0.1, port3306, usertest_user, passwordyour_password, databasetest_db, public_key_retrievalTrue )public_key_retrieval这个参数在 PyMySQL 1.0 及以上版本中可用老版本可能需要升级库版本。我见过一些人用旧版 PyMySQL 连接 MySQL 8 报错之后去网上找了一堆charset、use_unicode之类的配置其实都不是关键升级库版本或者加上这个参数才是正解。5.2 需求场景与方案匹配建议方案没有绝对的优劣关键是匹配需求。我把实际工作中几种典型场景和对应方案整理出来你可以直接对号入座场景一本地开发环境连接 MySQL 8手头就一台机器图省事。建议直接改 JDBC 连接串加参数不用动服务端。好处是服务端保持默认安全配置其他同事的行为不受影响。场景二老项目升级到 MySQL 8代码里几十处 JNDI 数据源配置不方便逐个改。建议直接从服务端把用户认证插件改回mysql_native_password一次性解决全部客户端兼容问题。场景三公司安全基线要求禁用明文传输和弱密码散列算法。建议坚持默认caching_sha2_password同时为生产环境配置 SSL或者至少开启公钥检索并在安全组层面限制数据库端口的来源 IP。场景四自动化运维脚本批量建库建用户。建议在脚本里创建用户时显式指定认证插件避免依赖全局默认值的变动。关于场景二多说一句老项目升级时还有一个隐形问题不只是认证插件变了数据库驱动版本也可能过旧。旧版 MySQL Connector/J比如 5.1.x对 MySQL 8 的支持并不理想建议顺手把驱动升级到官方推荐版本否则就算认证插件改回去了也可能遇到其他字符集、时区相关的连接警告或错误。6. 踩坑记录与排查技巧6.1 几个容易误判的场景这个报错看起来简单但在实际排查中我见过不少人在错误方向上浪费了大量时间。第一个容易误判的场景是把报错当成密码错误。因为报错信息里虽然没有直接说密码错但连接失败的结果是一样的很多人第一反应是去改密码结果改完照样报错。我在这里只想提醒一句遇到连接失败时先看完整的异常堆栈Public Key Retrieval is not allowed是明确的权限配置问题和密码验证失败是两码事。第二个容易误判的场景是在内网环境已经把allowPublicKeyRetrieval设置为 true 了但还是报错。这种情况大概率是连接串被中间层改写了比如你配置的是 JNDI 数据源但应用服务器的连接池把参数过滤掉了。检查方式是在数据库层面开通用日志或者干脆绕过连接池用一个独立的最小 Java 程序测试原始连接串。第三个误判场景是报错提示里出现了SSL connection error之类的信息然后网上搜出来还是allowPublicKeyRetrieval相关的调整方案结果改了还是不对。这种情况很可能是因为服务端的 SSL 证书配置有问题或者是客户端提供了无效的证书参数。先解决 SSL 握手问题再考虑公钥检索的事。6.2 快速定位问题出在服务端还是客户端我自己的排查顺序通常是这样第一步用命令行 mysql 客户端测试不加任何额外参数用同一账号连接一次。如果命令行也报Public Key Retrieval is not allowed说明问题在服务端侧的用户认证方式如果命令行能连上说明客户端程序或驱动参数有问题。第二步查看用户认证方式SELECT user, host, plugin FROM mysql.user WHERE user test_user;如果plugin显示的是caching_sha2_password而你的连接环境又没有 SSL那基本可以确定问题源头了。第三步检查服务端是否启用了 SSL。如果启用了 SSL客户端配置里却没跟上也会出现一堆奇怪的报错。可以用以下命令查看服务端 SSL 状态SHOW VARIABLES LIKE %ssl%;have_ssl为YES或DISABLED分别代表不同的情况。YES说明服务端支持 SSL 但连接还可以走非 SSLDISABLED说明服务端编译时就没带 SSL 支持。有了这些信息你就能判断问题到底发生在哪一层而不是盲目加参数。6.3 一个公钥检索报错的完整排查实录之前有个项目组部署新测试环境运维那边反馈应用连不上数据库报错信息就是这个。我当时按顺序做了一遍排查先用命令行连接发现报错一模一样说明不是应用代码的问题是服务端的用户配置问题。然后查了用户的 plugin果不其然是caching_sha2_password。再看了下服务端的 SSL 状态发现测试环境没有配置任何 SSL 证书have_ssl是DISABLED。到这里问题就很清楚了测试环境既没有加密通道客户端又不允许公钥检索两边无法完成安全的密码传输。因为这个测试环境的客户端是运维那边写好的脚本没法逐个改参数我直接选择把该用户的认证插件改成了mysql_native_password一条 SQL 下去连接恢复正常。整个过程花了不到十分钟。如果一开始就去查什么防火墙、网络连通性反而会把简单问题复杂化。7. 从安全角度聊聊 Public Key Retrieval7.1 为什么客户端默认不允许公钥检索看到这里你可能会问既然服务端支持提供公钥客户端默认关掉它是不是一种过度谨慎事实上不是。allowPublicKeyRetrieval默认关闭是有安全考量的。如果没有加密通道客户端向服务端请求公钥的过程本身就是明文传输中间人完全可以在这时候介入塞给你一把假的公钥。这样后续你用假公钥加密的密码就相当于直接暴露给了攻击者。这就好比你去银行办业务大堂经理递给你一个信封让你把密码放进去这个动作本身是合理的。但如果有个陌生人拦住你也说自己是银行工作人员给你一个信封让你套密码你就要警惕了。客户端默认拒绝“陌生人”递过来的信封这就是安全机制的本意。7.2 如何在安全性和便利性之间找到平衡如果不想在安全上妥协太多又不想被频繁的连接问题困扰最推荐的做法是启用 SSL 连接。一旦启用了 SSL整个连接过程就走加密隧道密码传输无需额外的 RSA 公钥检索caching_sha2_password也能正常工作。这种情况下allowPublicKeyRetrieval保持默认的 false 即可安全等级明显更高。具体到 JDBC 连接串启用 SSL 并允许公钥检索的写法长这样jdbc:mysql://127.0.0.1:3306/test_db?useSSLtrueserverSslCert/path/to/ca.pemallowPublicKeyRetrievaltrue注意这里allowPublicKeyRetrievaltrue仍然被保留但 SSL 通道建立后公钥检索主要用于某些特殊阶段的校验风险比裸连时小得多。如果你所在团队有专门的 DBA可以请对方生成一套内网专用的自签名证书这样开发环境和测试环境都能安全连接也不用每次都纠结公钥检索的问题。7.3 修改全局默认值前先评估影响面改动服务端的全局配置之前一定要先想清楚影响范围。比如你把default_authentication_plugin改成mysql_native_password后续所有新建用户都会走老插件而那些已经创建的用户不受影响。这看起来问题不大但如果你后面想要恢复到默认配置之前那些老用户还是老插件这就造成了环境之间的不一致排查问题时容易糊涂。我的习惯是服务端配置尽量少动优先通过客户端参数解决问题。只有在客户端无法统一控制或者历史包袱过重的场景下才考虑修改服务端全局配置。改之前先导出当前用户列表和认证方式作为备份改完之后记录到变更文档里方便后续跟踪。8. 个人经验小结与扩展思考关于这个报错我实际处理过不下几十次渐渐形成了一套自己的判断习惯。大多数情况下我会优先检查是不是 JDBC 连接串缺少参数因为这是出现频率最高的场景其次检查用户认证插件最后检查服务端 SSL 状态。这套顺序帮我省下过很多排查时间。还有一个细节我觉得值得分享如果同一台机器上装了多个版本的 MySQL 驱动踩坑概率会明显增加。有些老的 Java 项目WEB-INF/lib下面躺着一个老版本驱动应用服务器自身又带了一个新驱动类加载顺序不同会导致连接行为完全不同。遇到这种情况先清理重复依赖再调整参数配置否则你会在两个驱动之间来回被折磨。另外MySQL 8 的caching_sha2_password插件并不是什么“麻烦制造者”它的存在是为了提升整体安全性。只是它的默认行为和许多老客户端的默认配置存在一个“互相迁就”的问题。想通这一点你再看这个报错就会觉得很自然不是某个环节坏了只是两边没商量好怎么配合。最后留一个扩展思路如果你维护的系统很多建议整理一份标准的数据库连接配置模板把useSSL、allowPublicKeyRetrieval、serverTimezone等参数都显式写全发给团队统一使用。这些坑踩过一次就够了没必要让每个新来的同事都重新踩一遍。文档化这件事投入产出比极高。