MySQL 8.0连接报错Public Key Retrieval处理指南 先聊一个很典型的现场开发环境里新起了个MySQL 8.0实例应用服务一启动就连库失败控制台抛出一行红字Public Key Retrieval is not allowed。账号密码确认过无数次端口和权限也查了就是连不进去。第一次遇到这个问题的人很容易往账号授权、防火墙方向排查折腾半天才发现问题出在MySQL 8.0默认认证插件和客户端驱动默认参数上。这篇文章把这个报错的前因后果、各客户端的处理方式以及我实际踩过的几个坑完整梳理一遍下次再看到它你几分钟就能定位。这个报错主要影响的是使用JDBC、图形客户端或脚本语言连接MySQL 8.0数据库的场景后端开发、测试、运维都会碰到。它不解决其实也不影响你写SQL但它卡在“连接”这个第一步项目起不来、接口调不通属于那种看着不大但特别耽误事的错误。我会从原理讲到实操再给一堆排查经验尽可能让你看完就能直接照着改。1. 先认准报错是什么样、什么时候出现1.1 完整的错误现场还原Java应用里最常见的报错是这个样子java.sql.SQLException: Public Key Retrieval is not allowed at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:129) at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122) ...如果用的是Spring Boot日志里通常会配上这样一句HikariPool-1 - Exception during pool initialization. java.sql.SQLNonTransientConnectionException: Public Key Retrieval is not allowed注意后半段有的版本包装成SQLNonTransientConnectionException有的直接就是SQLException但关键的标志性文本始终是这一句Public Key Retrieval is not allowed。这个报错并不是密码错误。密码错误会返回Access denied那完全是另一条排查路径。它的触发点在于MySQL 8.0创建的默认用户认证插件大多是caching_sha2_password客户端在非SSL连接下需要先向服务端申请RSA公钥用来加密传输密码但客户端驱动出于安全考虑把“自动获取公钥”的开关默认关掉了。图形客户端也一样。用DBeaver、DataGrip这类工具连同一个库如果连接属性里没有勾选“允许公钥检索”一样会在连接测试阶段弹出一模一样的错误。命令行mysql客户端通常不会报因为它和MySQL共用一套底层客户端库公钥拉取逻辑早就处理好了。1.2 几个容易混淆的相似报错排查之前先别急着改参数务必确认你看到的确实是Public Key Retrieval别被长得像的错误带偏。我把几个高频容易混淆的报错整理在一起见下表。报错关键字真正原因特征Public Key Retrieval is not allowed非SSL连接下客户端禁止向服务端获取RSA公钥用户名密码通常没问题报错发生在认证阶段Access denied for user xxxyyy账号密码错误或host不匹配密码输错、授权来源IP不对时出现Connection refused端口不通、服务没起、防火墙拦截网络层问题telnet端口就能发现Communications link failure网络断开、连接超时、SSL握手失败常见于连接串网络参数配置不当SSL connection error服务端要求SSL但客户端没配证书紧跟在握手阶段风向不对判断的一个技巧是如果你用命令行mysql -h主机 -u用户 -p能正常连上但Java、Python或者某个图形工具连不上并且日志里出现了Public Key Retrieval is not allowed那基本就是认证插件和驱动参数的问题不用再怀疑权限了。2. 为什么会有这个错MySQL 8.0认证机制的变化2.1 默认认证插件从mysql_native_password换成了caching_sha2_passwordMySQL 5.7及更早版本默认的认证插件是mysql_native_password它把用户的密码做两次SHA1运算得到一个20字节的哈希值保存在mysql.user表的authentication_string字段里。这个方案实现简单、历史久、所有客户端都支持但安全性已经摆不上台面了因为无盐的SHA1哈希很容易被彩虹表加速破解而且整个认证过程中的挑战码机制也存在被中间人截获后离线爆破的风险。MySQL 8.0把默认插件换成了caching_sha2_password。它引入了加盐哈希、更长的摘要更重要的是在首次认证时增加了“获取RSA公钥并用公钥加密密码”的步骤。这一步直接决定了我们今天遇到的这个报错。你可以这样理解老方案的对话是“你说密码我用固定公式算一下对得上就过”新方案的对话是“你先拿我这把锁把密码锁进保险箱递给我我不会直接收明文”。问题就出在“先拿锁”这个环节客户端驱动默认不敢随便拿于是直接拒绝执行抛出了那句Public Key Retrieval is not allowed。2.2 一次完整认证握手里公钥发生在哪一步caching_sha2_password的完整认证过程拆开看大概是这几步客户端发起连接服务端回复“请用caching_sha2_password插件认证”。客户端进行快速认证尝试发送用服务端挑战数据加盐后的密码哈希。如果服务端内存缓存里已经有该用户的认证数据快速认证直接通过整个过程不需要RSA公钥。如果缓存里没有比如实例刚重启、用户第一次登录、缓存失效服务端返回一个状态码要求客户端执行“完整认证”。此时如果连接不是SSL加密的服务端会把RSA公钥发给客户端或者允许客户端主动来请求这把公钥。客户端用公钥加密密码提交给服务端服务端用私钥解密后校验。校验通过服务端把该用户的认证结果放入缓存后续同一实例上的连接走快速认证。从第6步能看到公钥获取并非每次连接都会发生这就是为什么有时候重启数据库后突然报错而之前好好的。连接池长时间运行、缓存热着新连接都走快速认证一旦缓存失效第一个新连接就要触发完整认证客户端如果禁用了公钥获取就会直接在连接池初始化时炸掉。2.3 客户端为什么默认禁止公钥检索那么这个报错的主角参数allowPublicKeyRetrieval为什么驱动默认是false官方这么设计主要是防中间人攻击。公钥从服务端传过来的时候如果连接本身没有加密理论上存在被替换的可能性。想象一下你以为拿到的是服务端那把锁但如果网络路径上有个恶意节点悄悄换了一把锁给你你毫无察觉地把密码“锁”进了对方的保险箱等于明文把密码交给了对方。所以驱动默认宁可让你明确授权也不愿默认信任一个来路不明的公钥。你可以把它理解为“对方说他是客服你至少得核对一下工牌而不是人家说什么你都信”。把这个逻辑反过来看你就明白为什么useSSLtrue能绕开这个错因为SSL连接本身已经验证了服务端身份密码走加密通道传输不需要再用RSA公钥来保护公钥获取步骤被跳过自然不涉及那个开关。2.4 缓存机制对报错出现时机的影响很多人在实际工作中会发现这个报错有时是“时灵时不灵”的今天连不上明天就能连上。这往往就是caching_sha2_password的缓存机制在起作用。MySQL服务端对每个用户有一个缓存的认证结果简单说就是“这位用户最近通过完整认证验证过”那么在缓存有效期内新连接就走快速认证路径。缓存一旦失效或者MySQL实例重启或者用户第一次登录某个新实例就会重新要求完整认证。如果客户端没有开公钥检索这个时点一撞上就报错。这里顺便说一个排查技巧如果你怀疑是这个原因可以观察报错是不是集中在“数据库刚重启之后”或者“用户第一次连接某个新实例”的时候配合SHOW STATUS LIKE Caching_sha2_password_%这类状态变量能辅助确认服务端缓存情况。3. 不同客户端的解法照着抄就行3.1 Java/JDBC连接串加两个参数别漏了Java系最常见的处理方式就是在JDBC URL里加上allowPublicKeyRetrievaltrue。如果你没有配置SSL通常还要显式加上useSSLfalse组合起来是这样jdbc:mysql://127.0.0.1:3306/demo?useSSLfalseallowPublicKeyRetrievaltrueserverTimezoneAsia/ShanghaicharacterEncodingutf8参数拆开说useSSLfalse明确告知驱动这条连接不走SSL加密。allowPublicKeyRetrievaltrue允许驱动在需要时向服务端请求RSA公钥。serverTimezoneMySQL 8.0驱动对时区感知更严格不设置容易引出另一个时区报错。如果配置了useSSLtrue并且证书环节正常那就不需要allowPublicKeyRetrievaltrue因为认证过程不再依赖RSA公钥。千万别两个参数同时设置得乱糟糟比如useSSLfalse和SSL证书配置同时出现后续会引来定位困难。Spring Boot项目如果使用的是spring.datasource.url直接在url里追加如果使用的是spring.datasource.hikari.jdbc-url也要同步修改注意别改错地方。3.2 图形客户端找到“允许公钥检索”选项以几个常见数据库工具举例处理方式大同小异核心都是把allowPublicKeyRetrieval打开。DBeaver里右键连接 → 编辑连接 → 驱动属性 → 找到allowPublicKeyRetrieval把值从false改成true然后测试连接。DataGrip的设置在连接配置界面右侧的“高级”标签页里可以搜索“Public Key”直接定位到该参数。Navicat新版对caching_sha2_password支持得比较好一般不需要手动改这项参数但如果你用的版本较老又连的是MySQL 8.0默认用户升级客户端是最省事的办法。有个细节值得说图形工具通常还会提供“服务器公钥”或“使用SSL”的单独配置项不要为了消掉报错盲目勾选“允许公钥检索”却忽略了SSL选项组合不对仍然会出问题。工具里如果勾了SSL但没导入证书报错会变成SSL握手失败跟当前的报错长得不一样排查方向完全不同。3.3 Python、Go、Node等场景怎么处理Python这边官方推荐的mysql-connector-python支持直接在连接参数里写import mysql.connector conn mysql.connector.connect( host127.0.0.1, userapp_user, passwordyour_password, databasedemo, allow_public_key_retrievalTrue, use_pureTrue )这里有个容易忽略的点allow_public_key_retrieval和allowPublicKeyRetrieval只是大小写和下划线命名的差别本质都是同一个开关。use_pureTrue是让驱动使用纯Python实现配合公钥获取更可靠。PyMySQL的话新版本对caching_sha2_password的支持已经比较成熟默认会尝试处理RSA公钥流程多数情况下不需要额外干预但如果你用的是老旧版本还是建议升级到当前稳定版。Go语言的go-sql-driver/mysql连接串中也有对应参数形如allowPublicKeyRetrievaltrueNode.js的mysql2同样在连接配置里支持这个选项。这已经是各大MySQL客户端的通用参数了你在语言对应的驱动文档里搜索这个关键词一般都能找到准确的位置。3.4 把认证插件改回mysql_native_password下策但有适用场景如果因为历史兼容问题实在没法定制客户端行为另一个常见方案是把用户认证插件改回mysql_native_passwordALTER USER app_user% IDENTIFIED WITH mysql_native_password BY your_password;改完之后这个用户走老式SHA1哈希认证绝大多数老客户端都能直接连上不会再有公钥获取的问题。但这个方案要谨慎。mysql_native_password本身是MySQL 8.0官方明确建议逐步弃用的插件安全性远不如caching_sha2_password。如果你负责的是内部开发环境、临时脚本可以为了省事临时改如果是生产环境、涉敏数据不建议为了兼容性把认证等级降回来应该优先考虑升级客户端、配置SSL这才是合规且安全的方向。还有一个更窄的适用场景某些数据库中间件、老版本连接池、第三方商业组件它们内置的MySQL驱动版本太老根本不认识caching_sha2_password代码也不是你想改就能改的。这个时候临时把用户切回老插件是代价最低的上线方案。3.5 一步到位直接使用SSL连接比起打开公钥获取更稳妥的长期方案是给连接配置SSL。用SSL之后密码通过加密通道传输服务端身份通过证书校验认证过程不需要RSA公钥allowPublicKeyRetrieval也就不需要了。代价是需要准备证书、维护证书信任链还会有一点点连接性能开销。对于大多数外部访问数据库的场景我建议优先考虑。如果是开发环境、内网快速联调直接加allowPublicKeyRetrievaltrue更省事两种方式都合理按场景取舍即可。4. 三个真实排查现场和一套速查流程4.1 现场一测试环境连接池初始化集体报错某次给测试环境升级数据库把MySQL 5.7平滑迁移到8.0数据导完、账号建好重启应用后一看日志连接池全满屏红。当时第一反应是检查账号授权登录MySQL执行SHOW GRANTS发现权限完全正常再用命令行mysql -h测试库地址 -u应用用户 -p手动连接密码也正确。折腾了快二十分钟才猛然想到升级之前的老实例默认是mysql_native_password而新建的8.0实例里重建的应用账号直接被默认插件caching_sha2_password接管。旧应用用的连接串没有任何关于认证方式的调整驱动一上来就走老式认证结果撞上了公钥获取开关。解决办法就是在应用配置的JDBC URL末尾加上?useSSLfalseallowPublicKeyRetrievaltrue重启后连接池正常初始化。这个案例的教训就是数据库大版本升级后不能只关注数据迁移和账号权限客户端驱动与认证插件的兼容性同样要列入检查清单。4.2 现场二同一个库、同一个账号工具能连但程序连不上还有一个常见现象DBeaver里明明连接测试通过Java程序却报Public Key Retrieval is not allowed。原因是多数图形客户端在创建连接时要么默认就勾选了“允许公钥检索”要么用户在界面里顺手点过。而程序里是裸的连接串什么都没有设置。同一个服务端账号两种客户端表现出完全不同的行为遇到这种情况不要怀疑数据库有问题先去核对程序连接串里的参数。这里扩展一个思路排查问题时先用工具连一次、再用命令行连一次、最后看程序报错这种“多客户端交叉验证”的方式能快速圈定问题范围。如果三个客户端都报同样的错重点怀疑服务端只有程序报、工具不报重点转向驱动参数和版本。4.3 现场三参数加了还是报错多半是连接串没生效我以前还踩过一个坑在Spring Boot配置里改了spring.datasource.url加了allowPublicKeyRetrievaltrue结果启动还是报同样的错。后来发现项目里其实配置了两个数据源一个走spring.datasource.url另一个走自定义的ConfigurationProperties绑定只有后面那个才是MyBatis等核心组件实际使用的数据源。我只改了明面上看到的那一个真正的连接串还是旧的。建议在排查时先确认程序实际拿到的JDBC URL是什么。可以临时在配置类里加一行日志打印dataSource.getConnection().getMetaData().getURL()或者检查连接池的监控页面。连接池在创建连接时还会做参数解析个别参数加在URL末尾被连接池拦截后未必能透传到底层驱动遇到这类情况需要查阅连接池版本对参数透传的支持情况。4.4 一套通用的排查速查流程把各类排查经验收敛成一套简单流程照着走基本不会卡太久先用命令行mysql -u用户 -p -h主机试连确认账号、密码、网络都没问题。对比命令行能连而程序/工具不能连的情况把嫌疑集中到客户端驱动参数。查看当前用户的认证插件SELECT user, host, plugin FROM mysql.user WHERE user你的用户;如果是caching_sha2_password检查连接串或驱动属性里的allowPublicKeyRetrieval。不想开公钥获取就检查SSL配置是否合理或者考虑临时改认证插件。改完参数重启应用验证连接池初始化日志。这套流程处理了这个报错不下十次基本每次都能在几分钟内定位。5. 延伸几条实用经验账号、连接池与团队规范5.1 查看和修改用户认证插件的常用SQL再总结几条操作性的SQL方便排查-- 查看用户的认证插件 SELECT user, host, plugin FROM mysql.user WHERE user app_user; -- 修改认证插件为mysql_native_password谨慎使用 ALTER USER app_user% IDENTIFIED WITH mysql_native_password BY 新密码; -- 修改认证插件为caching_sha2_passwordMySQL 8.0默认 ALTER USER app_user% IDENTIFIED WITH caching_sha2_password BY 新密码; -- 刷新权限ALTER USER方式通常不需要但执行无妨 FLUSH PRIVILEGES;这里注意一个细节ALTER USER ... IDENTIFIED WITH ... BY ...和ALTER USER ... IDENTIFIED BY ...的语义不同。前者明确指定插件适合在插件之间切换后者保持当前插件不变只改密码。如果在切换插件时忘了指定WITH子句可能只是改了密码插件根本没变问题依旧排查时容易被这个细节点坑到。5.2 生产环境与连接池场景下的推荐配置生产环境如果用的是caching_sha2_password我一般这样设计连接层应用与数据库之间优先走内网能用SSL就用SSL不需要打开allowPublicKeyRetrieval。确需开启allowPublicKeyRetrievaltrue要清楚这等于放弃了服务端公钥身份校验仅在可信网络里使用。连接池预热connection-test-query保持简单比如SELECT 1。同时把连接池的最小空闲连接数设到业务合理值让连接在初始化阶段就触发完整认证避免运行时第一个请求被公钥获取问题拖累。保持驱动版本跟MySQL 8.0匹配老驱动对新认证插件的语义理解不完整有些参数加了也没用。这些配置之间没有绝对标准答案但思路是统一的先把认证路径搞干净再考虑连接池性能与稳定性。5.3 新项目初始化时怎么避开这个坑新项目如果确定使用MySQL 8.0建库建账号时就可以定好统一规范建议直接采用caching_sha2_password客户端一律使用新版驱动。应用连接串里明确写入allowPublicKeyRetrievaltrue或者在网络策略允许的情况下直接配SSL。团队内部把这段连接串沉淀成模板项目初始化时复制即可避免每个人踩一遍同一个报错。线上环境如果数据库和中间件之间有严格的安全策略涉及证书分发、端口限制、网络隔离等这些都要提前和基础设施团队对齐。现在很多云数据库实例默认就开SSL连接串如果不带相关参数可能第一步就失败那又是另一类问题了。就我自己的习惯来说只要条件允许生产环境都优先考虑SSL连接。开发环境为了快速迭代可以在JDBC URL里固定把useSSLfalseallowPublicKeyRetrievaltrue写明简单省事但也明确知道自己放弃了什么。这组参数背后承载的安全权衡我觉得每个天天和MySQL打交道的人都值得花十分钟理解一下。