做成一个资深博主的口吻。第一次遇到这条报错,是在新部署的 MySQL 8.0 环境上。Navicat 连接时弹窗直接抛出来,后台日志刷了一屏“Public Key Retrieval is not allowed”。当时第一反应是密码写错或者账号权限不对,折腾半天才发现,问题出在 MySQL 8 默认的认证插件 caching_sha2_password 和客户端公钥获取策略上。要解决它,得先把这条英文背后的机制弄明白。
1. 先搞清楚报错背后的原因
1.1 caching_sha2_password 是怎么回事
MySQL 5.7 及更早版本默认使用 mysql_native_password 认证插件,密码哈希基于 SHA1。MySQL 8.0 开始默认使用 caching_sha2_password,密码哈希使用 SHA256,并且引入了“服务端缓存认证结果”的快速路径。名字里的 caching 指的就是这种缓存机制:同一个账号短时间重复登录时,服务端可以直接复用上一次验证过的结果,不需要每次都做完整密码运算。这个插件安全性更强,却给第三方客户端带来了兼容性负担。
打个比方:mysql_native_password 像是用一把固定钥匙开门,钥匙对得上就行;caching_sha2_password 更像一种“需要协商”的智能锁,如果链路没有加密,它必须先确认你有能力接收一把临时公钥,再用这把公钥加密密码传输。这个“确认”环节如果客户端不配合,连接就直接断掉。报错内容看起来和密码毫无关系,实际上密码从来没被校验过——连接还没走到校验密码那一步。
1.2 为什么客户端拿不到公钥
继续刚才的场景。当 MySQL 服务端使用 caching_sha2_password,并且客户端没有走 SSL/TLS 加密通道时,服务端需要把自己的 RSA 公钥发给客户端,用于在明文链路上加密密码。但服务端不会像广播一样直接甩公钥出来,因为公钥下发过程本身存在被中间人劫持替换的风险。标准做法是客户端先显式声明“我允许获取服务端公钥”,服务端才肯发。
这里有个很容易误解的点:公钥检索不是服务端拒绝发送,而是客户端默认不允许接收。以 JDBC 为例,Connector/J 的 allowPublicKeyRetrieval 属性默认就是 false。URL 里不加这个参数,客户端就不会向服务端发起公钥请求,握手在认证阶段就中止,于是抛出“Public Key Retrieval is not allowed”。
如果连接走的是 SSL 通道,情况完全不同。密码会直接通过 TLS 加密传输,服务端不需要额外下发 RSA 公钥,自然也不会有这个报错。这也是为什么有些人把 useSSL 打开后,问题突然就消失了。但内网开发环境大多默认不开 SSL,所以真正需要解决的,还是让客户端正确拿到公钥,或者干脆改用安全链路。
提示:allowPublicKeyRetrieval=true 只影响“允许客户端向服务端请求 RSA 公钥”这件事,它不等于明文密码传输,密码仍然会用公钥加密后再走网络。但公钥本身是在网络上传输的,因此这个选项默认关闭。
2. 最容易踩坑的几种连接场景
2.1 JDBC 连接:Java 项目最常见的翻车现场
Java 项目遇到这个报错的频率最高。原因很直接:MySQL 8.0 发布后,很多项目只是把驱动版本从 mysql-connector-java 5.x 换到 8.x,连接 URL 还是老一套,并没有跟上新认证机制的要求。第一次运行就会看到类似这样的异常:
java.sql.SQLException: Public Key Retrieval is not allowed这不代表数据库账号密码有问题。你可以先用 MySQL 命令行用同一个账号密码试一下,如果命令行能连上,而 JDBC 不行,基本可以锁定是连接参数缺失。
JDBC 最省事的写法是在 URL 后面追加两个参数:
jdbc:mysql://127.0.0.1:3306/mydb?useSSL=false&allowPublicKeyRetrieval=trueallowPublicKeyRetrieval=true 允许客户端向服务端请求公钥,useSSL=false 明确告诉驱动当前连接不启用 SSL。这两个参数经常配套出现,因为连接本身没有加密通道时,才需要公钥检索;如果已经开了 SSL,公钥检索通常就不需要了。
代码里也可以用 Properties 设置,效果是一样的:
String url = "jdbc:mysql://localhost:3306/mydb"; Properties props = new Properties(); props.setProperty("user", "root"); props.setProperty("password", "你的密码"); props.setProperty("useSSL", "false"); props.setProperty("allowPublicKeyRetrieval", "true"); Connection conn = DriverManager.getConnection(url, props);Spring Boot 中如果使用默认的 HikariCP,同样是在数据源 URL 里加参数:
spring.datasource.url=jdbc:mysql://localhost:3306/mydb?useSSL=false&allowPublicKeyRetrieval=true spring.datasource.username=root spring.datasource.password=你的密码如果你用 Maven 管理依赖,注意驱动坐标的变化,MySQL 8.0.31 之后的官方驱动 artifactId 已经改成 mysql-connector-j:
<dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <version>8.0.33</version> </dependency>老版本 mysql-connector-java 虽然也能用,但版本太旧时可能连 allowPublicKeyRetrieval 这个参数都不认识,参数写了也会被当成未知属性忽略。这一点在排查时很容易被漏掉。
2.2 Navicat、DBeaver 等图形客户端的处理方式
用图形客户端连 MySQL 8 时,报错弹窗很短,但菜单里其实藏了对应的开关。Navicat 的连接配置里通常可以在“高级”或“SSL”标签中找到类似“允许公钥检索”的选项,不同语言版本叫法略有差异,英文界面一般是 Allow Public Key Retrieval。勾上之后重新连接,问题通常就解决了。
DBeaver 稍微绕一点。它默认使用自己的连接协议,连接时会走驱动属性。操作方法是在连接编辑界面进入“驱动属性”页,添加一个自定义属性:
- 属性名:allowPublicKeyRetrieval
- 值:true
如果有需要,还可以再加一个 useSSL=false,避免驱动默认行为干扰。DBeaver 的驱动属性是全局缓存的,改完以后最好点一下“重置连接”再测试,因为旧连接可能缓存了失败的握手状态。
还有一个容易忽略的点:如果用的是 MySQL Workbench,它也有 SSL 相关选项,在没有勾选 SSL 的默认连接下,遇到这个报错的概率也不小。把连接方式从不使用 SSL 改为“如果需要”或勾选“允许公钥检索”即可。注意,这类 GUI 工具的选项位置在每个版本里都可能移动,别死记菜单路径,关键是理解它需要的是“允许公钥检索”这个动作。
2.3 命令行连接和脚本连接
官方 mysql 命令行客户端其实很少报这个错,因为它在身份认证阶段默认会尝试处理公钥请求。但我见过一些基于老版本 libmysqlclient 的脚本工具、PHP 扩展或者 ODBC 驱动,在连接 MySQL 8 时一样会触发。命令行场景下,可以用两个办法之一解决:
使用 --get-server-public-key 参数,显式允许客户端获取服务端公钥:
mysql -h 192.168.1.10 -u appuser -p --get-server-public-key或者强制走 SSL:
mysql -h 192.168.1.10 -u appuser -p --ssl-mode=REQUIREDMySQL 8.0 客户端使用 --ssl-mode=REQUIRED,5.7 及更早版本通常用 --ssl=1。如果你的客户端还不支持这些参数,那就得从驱动或库的底层连接选项下手,比如在 ODBC 连接串里加上对应的公钥获取开关,或者干脆换用较新版本客户端库。
这里想强调的是:脚本连接报错时,先判断这个脚本调用的到底是哪个客户端库。不同语言的驱动支持程度千差万别,有的新驱动会自动处理公钥检索,有的老驱动则需要手动指定参数,还有一些驱动必须升级之后才能识别 caching_sha2_password。“报错相同、解法不同”是这类问题的常态。
3. 实操:从根上解决这个问题
3.1 方案一:连接参数里显式允许公钥获取
这是影响面最小、恢复速度最快的方案。它不改变数据库账号、不重启服务、不影响其他人,只需要在连接端把公钥获取开关打开。适合开发环境、临时调试、以及所有“先让系统跑起来”的场景。
具体到你的客户端或驱动:
- JDBC:URL 加 allowPublicKeyRetrieval=true
- Navicat:勾选允许公钥检索
- DBeaver:驱动属性里加 allowPublicKeyRetrieval
- ODBC:连接串里设置 AllowPublicKeyRetrieval=1,不同驱动版本写法略有差异
补充说明一个细节:allowPublicKeyRetrieval=true 只在连接没有走 SSL 的时候才会真正发挥作用。如果连接已经通过 SSL 加密,公钥检索步骤会被跳过,就算参数存在也没有实际意义。所以别以为加了它就要强制放弃 SSL,两者不是互斥关系,只是针对不同的链路状态。
从安全角度看,允许公钥检索确实有中间人风险。公钥在网络上明文传输时,攻击者如果在中间链路,可能替换成自己的公钥,从而截获加密后的密码。这就是为什么这个选项默认关闭。开发环境、内网可信网络可以放开;公网、生产环境最好还是走 SSL,不要一味靠 allowPublicKeyRetrieval 硬撑。
注意:如果在公网环境中长期使用 allowPublicKeyRetrieval=true,且同时关闭 SSL,密码理论上仍可能被中间人截获。这个组合只适合可信内网,不能当生产环境的长期连接方式。
3.2 方案二:把认证插件改回 mysql_native_password
如果你的环境里有大量老代码、老客户端,升级驱动或者改连接参数的成本很高,可以临时把账号认证插件改回 mysql_native_password。下面这条 SQL 就能改指定账号:
ALTER USER 'appuser'@'%' IDENTIFIED WITH mysql_native_password BY '新的密码';改完以后不需要执行 FLUSH PRIVILEGES,ALTER USER 会立即生效。可以用下面这条语句验证:
SELECT user, host, plugin FROM mysql.user WHERE user = 'appuser';执行结果里的 plugin 列应该变成 mysql_native_password。之后原来报错的客户端,理论上不需要改参数就能连上,因为这些客户端本来就熟悉老插件的握手流程。
更粗暴的全局做法是在配置文件里改默认认证插件:
[mysqld] default_authentication_plugin=mysql_native_password修改后需要重启 MySQL 服务,并且只影响之后新建的用户或显式 ALTER 过的用户。那些已经是 caching_sha2_password 的老用户不会自动切换,得逐个 ALTER。
这里必须泼一盆冷水:mysql_native_password 从 MySQL 8.0 起已经被官方标记为废弃,MySQL 8.4 开始默认禁用这个插件,后续版本只会越收越紧。把它当长期方案,等于把系统焊死在旧兼容层上。我的建议是:只作为过渡手段,最终目标还是升级客户端、使用 caching_sha2_password + SSL 或公钥检索。
3.3 方案三:给连接加上 SSL/TLS
第三种方案最接近官方预期:用 SSL 加密整个连接。SSL 通道一旦建立,密码传输天然是加密的,caching_sha2_password 不需要额外走公钥交换,报错自然消失。对生产环境、公网环境,这也是最合规的做法。
MySQL 8.0 服务端默认支持 SSL,可以用下面这条命令确认:
SHOW VARIABLES LIKE 'have_ssl';如果结果是 YES,说明服务端已经具备 SSL 能力。客户端连接时,需要指定 SSL 模式。在 JDBC 8.x 驱动里,常用参数是 sslMode,取值包括:
- DISABLED:不使用 SSL,等价于 useSSL=false
- PREFERRED:优先使用 SSL,服务端不支持时降级为明文
- REQUIRED:必须使用 SSL,但不校验证书
- VERIFY_CA:必须使用 SSL,并且校验服务端证书是否由受信任 CA 签发
- VERIFY_IDENTITY:比 VERIFY_CA 更进一步,还会校验证书里的主机名
开发环境用 PREFERRED 或 REQUIRED 就够了。连接串这样写:
jdbc:mysql://localhost:3306/mydb?sslMode=REQUIRED注意,旧版本驱动不认识 sslMode 参数,需要升级驱动。此外,如果使用自签名证书,VERIFY_CA 和 VERIFY_IDENTITY 还需要额外配置 truststore,这会让部署复杂度明显上升。单机房内网环境下,可以先从 REQUIRED 开始。
3.4 怎么确认当前用户的认证插件
排查时容易被忽略的是:全局默认是对的,但某个具体用户被建成了特殊插件。在 MySQL 8.0 里创建用户时,如果不指定插件,默认跟着全局配置走;但如果你在云数据库上操作,控制台可能默认生成的就是 caching_sha2_password,甚至 auth_socket,所以不能只盯着 my.cnf。
用这一条 SQL 把所有用户的插件列出来:
SELECT user, host, plugin FROM mysql.user ORDER BY user;重点看连接时用的那个账号,plugin 是不是 caching_sha2_password。如果确实是,再看它是否要求 SSL:
SHOW CREATE USER 'appuser'@'%';输出里如果包含 REQUIRE SSL,那么这个账号本来就强制使用 SSL,客户端普通连接方式自然会被拒。这种情况和“Public Key Retrieval is not allowed”是两回事,但会叠加在一起出现,容易让人误判。先把用户维度的信息确认干净,再去改连接参数,才不会做无用功。
4. 问题排查思路与速查表
4.1 先看报错上下文
遇到连接报错,我习惯先别急着复制错误内容去搜索,而是把报错前后的日志完整截下来。“Public Key Retrieval is not allowed”这句话虽然很具体,但并不能告诉你所有信息。前面可能有 Access denied,后面可能有 SSL 相关告警,这些组合起来才是完整线索。
比如 JDBC 场景,如果报错抛在连接池初始化阶段,那要检查的是数据源配置;如果连接池运行了十几分钟后突然开始报错,则要怀疑是不是账号密码被改了、认证缓存失效、或者网络中间设备拦截了新的握手。单看一句话,很容易把方向带偏。
4.2 排查顺序其实很固定
我的排查顺序基本固定为五步:
- 用 MySQL 官方命令行在数据库服务器本机尝试登录,排除账号权限和服务本身的问题;
- 查询目标用户的 plugin,确认是不是 caching_sha2_password;
- 用 SHOW VARIABLES LIKE 'have_ssl' 确认服务端是否支持 SSL;
- 检查客户端驱动版本,确认是否支持新的认证插件和连接参数;
- 根据场景选择 allowPublicKeyRetrieval=true 或启用 SSL。
这五步全部做完,最多不到十分钟。大部分时候走到第二步就能锁定方向,根本不用把整个配置文件翻一遍。
4.3 常见问题速查表
| 报错场景 | 常见原因 | 推荐处理 |
|---|---|---|
| JDBC 连接 MySQL 8 报 Public Key Retrieval is not allowed | URL 缺少 allowPublicKeyRetrieval=true | 连接串加 useSSL=false&allowPublicKeyRetrieval=true |
| Navicat 连接 MySQL 8 报同样错误 | 高级设置未勾选允许公钥检索 | 打开连接属性,勾选 Allow Public Key Retrieval |
| DBeaver 连接报错 | 驱动属性未设置公钥获取 | 驱动属性里添加 allowPublicKeyRetrieval=true |
| 服务端支持 SSL,客户端仍报错 | 客户端没有开启 SSL 模式 | JDBC 加 sslMode=REQUIRED,命令行用 --ssl-mode=REQUIRED |
| 老客户端不支持 caching_sha2_password | 客户端驱动版本过旧 | 升级驱动,或临时 ALTER 用户为 mysql_native_password |
| 云数据库连接报错 | 控制台账号强制使用 SSL 或特定插件 | 先查看账号的 REQUIRE SSL / plugin,再根据要求配置客户端 |
这张表解决的是最常见的六个入口。实际生产里还会遇到“连接超时后偶然出现一次”的奇怪情况,这种往往不是认证插件本身的问题,而是连接池里的旧连接失效后重新握手,新的握手走了非 SSL 路径导致的。连接池配置里加上连接有效性验证,能减少这类偶发报错对业务的影响。
5. 一些个人经验和避坑建议
5.1 我当前的默认习惯
我在实际项目里踩过几轮这个坑之后,慢慢形成了自己的固定做法。开发环境,数据库一般就在本机或公司内网,我会直接在连接串里写 useSSL=false&allowPublicKeyRetrieval=true,简单直接,不用处理证书,也不用担心驱动版本差异。测试环境,如果数据库允许,我会尽量开 SSL,用 sslMode=REQUIRED 起步,避免证书校验带来的额外配置。生产环境,尤其是跨公网访问数据库的场景,SSL 是标配,账号插件保留 caching_sha2_password,连接参数用 VERIFY_CA 或 VERIFY_IDENTITY,配合服务端正式证书。
这套组合的好处是:开发环境不会被这个报错卡住,生产环境又能满足安全要求。很多人一上来就把 allowPublicKeyRetrieval=true 往生产环境塞,虽然也能连,但中间人风险是实打实的。公网传输的密码如果被第三方截获,再强的 SHA256 也救不了。
5.2 别轻易动全局认证插件
最后再分享一个反面教训。有一回为了兼容一个老系统,我在 MySQL 8.0 的配置文件里直接设置了 default_authentication_plugin=mysql_native_password,重启之后老系统确实能连了,但两周后新的业务模块上线,安全基线检查直接报了“认证插件不符合安全基线”,整个数据库差点被打回重做。后来我花了不少时间把这个全局配置改回来,再逐个调整账号和客户端参数,才恢复正常。
MySQL 8.0 默认的 caching_sha2_password 并不是为了制造麻烦,它背后是一整套更安全的认证机制。遇到 Public Key Retrieval is not allowed,先判断能不能用 allowPublicKeyRetrieval 快速绕过,再判断要不要上 SSL,最后才考虑改认证插件。顺序对了,问题解决得很快;顺序反了,往往修完一个又冒出一个。这个报错本身不算难,难的是从“明文链路 + 默认不取公钥”这个前提出发,把客户端和服务端的身份认证链路一次性理顺。