资讯详情

资讯详情

MySQL 8 JDBC报错Public Key Retrieval is not allowed的原因与5种解法

1. 这个问题到底是什么报错场景重现与认知校准遇到Public Key Retrieval is not allowed这个报错的人十有八九是第一次把项目从 MySQL 5.7 迁到 MySQL 8或者在新环境里第一次用 JDBC 连接 MySQL 8 实例。报错信息很短短到让人摸不着头脑——它说的是“不允许检索公钥”但你的第一反应大概率是“我连密码都没输错跟公钥有什么关系”。先把报错场景完整重现一遍。假设你用 Java 8 MySQL Connector/J 8.x 连接本地 MySQL 8.0 数据库JDBC URL 长这样jdbc:mysql://localhost:3306/testdb?useSSLfalseserverTimezoneAsia/Shanghai执行DriverManager.getConnection(url, root, yourpassword)控制台立刻抛出异常堆栈核心信息如下java.sql.SQLException: Public Key Retrieval is not allowed at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:120) at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:95)如果你对比过 5.7 和 8.0 的默认行为会发现 5.7 时代很少听说这个错误。原因在于 MySQL 8.0 把默认认证插件从mysql_native_password换成了caching_sha2_password而这两种插件在“客户端如何验证服务端身份”这件事上走了完全不同的路。老的mysql_native_password插件验证密码时不需要额外的密钥交换环节客户端拿着密码哈希直接比对就行新的caching_sha2_password为了更安全的传输在非 SSL 连接下要求先获取服务器的 RSA 公钥用公钥加密密码再发给服务端。问题就出在“获取公钥”这一步。MySQL 连接器在非 SSL 模式下默认不允许向服务器请求 RSA 公钥因为公钥传输本身没有加密保护存在被中间人篡改的风险。所以驱动直接拒绝执行公钥检索报错就抛出来了。换句话说不是你的密码错了不是端口错了也不是账号权限有问题而是驱动在安全策略上“卡”住了。这个报错只影响非 SSL 连接吗不完全是。如果你开启了 SSLuseSSLtrue且服务端配好了证书整个认证过程走 TLS 加密通道公钥可以通过加密链路安全传递驱动不会触发这个限制。但大多数开发环境、测试环境、内网快速联调场景不会专门去配 SSL 证书于是错误就成了 MySQL 8 升级路上第一道拦路虎。搞清楚这条因果链之后解决思路就非常清晰了要么在连接参数层面允许公钥检索要么回到旧版认证插件要么干脆把连接升级成 SSL。下文从原因到方案给你一套完整的排查路径。2. 根因深挖MySQL 8 认证插件机制到底改了什么要真正理解这类问题并且以后不再慌你需要知道 MySQL 8 的用户认证体系比 5.7 多了哪些东西。我尽量用讲人话的方式拆解。2.1 caching_sha2_password 与 mysql_native_password 的本质区别MySQL 5.7 及更早版本用户密码默认使用mysql_native_password插件认证。它基于 SHA-1 哈希客户端把密码做哈希之后发给服务端比对。整个过程不涉及密钥协商服务端也不需要把任何公钥发给客户端简单直接但安全性一般——SHA-1 算法本身已经存在已知弱点而且密码哈希在传输链路中的保护能力有限。MySQL 8.0 引入caching_sha2_password作为默认认证插件它做的事情比老插件多两步首次认证时服务端生成一个 2048 位 RSA 密钥对私钥留在服务端公钥可以被客户端请求获取客户端拿到公钥后把“密码 随机数”用公钥加密传输给服务端服务端用私钥解密完成密码校验。如果连接已经启用 SSL/TLS握手过程本身就加密了RSA 公钥可以直接通过安全通道传给客户端不涉及额外风险。但如果没有 SSL公钥就得走明文。驱动面对这个场景时默认选择“安全优先”——我宁可报错也不让公钥裸奔在网络里。你可以把caching_sha2_password理解为升级版的“密码保险箱”它比旧保险箱安全但开锁流程多了一步“拿钥匙”。驱动默认不让你去拿这把钥匙于是你被锁在门外。2.2 公钥传输的安全博弈为什么驱动默认拒绝这里有个非常容易被人忽略的点Public Key Retrieval is not allowed本质上是客户端驱动的一种安全策略不是 MySQL 服务端的限制。服务端默认是愿意把公钥发给客户端的前提是客户端主动请求。但连接器 JDBC 实现里有一个开关叫allowPublicKeyRetrieval默认值是false。这个开关的语义是“是否允许客户端向服务器自动请求 RSA 公钥”。把它设为true驱动就允许在非 SSL 连接下拉取公钥完成认证。那么驱动作者为什么不默认开启这个开关因为在非加密链路上传输公钥存在中间人攻击风险——攻击者可以在服务器和客户端之间拦截公钥替换成自己的公钥然后客户端用攻击者的公钥加密密码攻击者截获密文后用自己的私钥解密密码就泄露了。这是标准的“中间人窃听”攻击模型。但在大多数实际场景中开发环境、内网测试环境、云数据库 VPC 内部网络攻击者要想实施中间人攻击并不容易。你可以把风险理解为“概率低、后果严重”而allowPublicKeyRetrievaltrue就是在便利性和安全性之间做了一个可接受的平衡。生产环境如果走公网强烈建议用 SSL 而不是开这个开关。2.3 不同连接方式受影响的差异这个报错不只是出现在 JDBC 连接里凡是基于 MySQL 官方协议实现的客户端都可能触发。常见的几类场景Java 应用使用 MySQL Connector/J 8.x这是最经典的重灾区Python 使用 mysql-connector-python如果版本较新且连接参数里allow_public_key_retrieval没设也会报类似的错某些 GUI 工具比如 DBeaver、DataGrip底层走 JDBC连接 MySQL 8 时也可能弹出该错误Node.js 的 mysql2 驱动对公钥拉取策略也有自己的处理不过因为它默认做了兼容层报这个错的相对少。有意思的是如果你的账号认证插件依旧是mysql_native_password比如从 5.7 升级实例时保留了旧账号那么即使 MySQL 服务端是 8.0JDBC 连接也不会触发公钥检索逻辑因为老插件的握手协议不需要 RSA 公钥交换。这也是为什么有些项目升到 MySQL 8 之后应用照常跑——因为账号插件没换。所以你在排查时记住一个判断标准报这个错的人账号一定是用了新插件换回老插件的人问题立刻消失。下文五种方法里有两条路径的核心逻辑就是这个。3. 方法一在 JDBC URL 中开启公钥检索开关最快见效这个方法是所有方案里最省事的适合开发环境、测试环境以及内网可信网络。只需要往 JDBC URL 后面追加一个参数jdbc:mysql://localhost:3306/testdb?useSSLfalseallowPublicKeyRetrievaltrueserverTimezoneAsia/Shanghai核心参数就是allowPublicKeyRetrievaltrue。加完之后不需要重启 MySQL 服务端不需要改账号不需要改代码逻辑重新运行应用就能连上。3.1 参数放置位置与注意事项JDBC URL 参数是从?开始多个参数用分隔这里有两个细节容易踩坑如果你用的是 Spring Boot 配置文件application.yml / application.propertiesURL 里如果本身已经带了?参数比如?useSSLfalse追加新参数时直接在后面加allowPublicKeyRetrievaltrue不要重复写?如果你在代码里通过Properties对象传参也可以这样写Properties props new Properties(); props.setProperty(user, root); props.setProperty(password, yourpassword); props.setProperty(allowPublicKeyRetrieval, true); props.setProperty(useSSL, false); Connection conn DriverManager.getConnection( jdbc:mysql://localhost:3306/testdb, props);两者的底层效果一样选择你代码里更好维护的方式就行。3.2 这个参数在 HikariCP / Druid 连接池里怎么配大多数生产项目不是直接用DriverManager拿连接而是通过连接池。以最常见的 HikariCP 为例配置项是这样写的spring: datasource: url: jdbc:mysql://localhost:3306/testdb?useSSLfalseallowPublicKeyRetrievaltrueserverTimezoneAsia/Shanghai username: root password: yourpassword driver-class-name: com.mysql.cj.jdbc.DriverDruid 的配置也类似把allowPublicKeyRetrievaltrue放到 URL 后面即可。注意有些老项目的 Druid 版本里useSSLfalse并不会自动允许公钥拉取必须显式加这个参数。3.3 方法一的风险边界什么时候不建议用先给结论生产环境且走公网连接时不建议开启allowPublicKeyRetrievaltrue并用非 SSL 链路。原因前面讲过公钥明文传输存在理论上的中间人窃听风险。但在以下场景你可以放心开本地开发环境公司内网 VPC 内部数据库与应用部署在同一台机器云数据库开启了白名单限制且网络链路受云平台安全组保护。有人在网上争论“这个参数会不会导致密码泄露”其实泄露的前提是攻击者已经能劫持你的网络流量。如果你的网络环境已经危险到这个程度那 SSL 证书也会面临同样的信任问题。安全是一个分层问题allowPublicKeyRetrievaltrue只是把风险等级从“绝对安全”降到“常规可信网络下的安全”你对自家内网的信任度决定这个参数适不适合你。4. 方法二切换认证插件回 mysql_native_password经典兼容法这个方法在很多老项目里是刚需因为不是所有人都能随便改代码、改配置。如果你手头有权限操作 MySQL 用户直接把账号认证插件换回mysql_native_password连接端零改动就能解决。4.1 查看当前账号的认证插件先登录 MySQL 命令行SELECT user, host, plugin FROM mysql.user WHERE user root;输出示例---------------------------------------- | user | host | plugin | ---------------------------------------- | root | % | caching_sha2_password | ----------------------------------------看到caching_sha2_password就说明账号已经在用新插件了。把它改成老插件的 SQL 如下ALTER USER root% IDENTIFIED WITH mysql_native_password BY yourpassword; FLUSH PRIVILEGES;执行完之后再跑一次查询插件应该变成mysql_native_password。这时候 JDBC 连接不会再要求 RSA 公钥交换问题消失。4.2 为什么这个方法“简单”但“有代价”代价之一就是安全性降级。mysql_native_password基于 SHA-1MySQL 官方在 8.0 里保留它纯粹是为了兼容老客户端同时官方文档里明确标注了它是弃用状态。更高版本的 MySQL比如 8.4 及以上已经把mysql_native_password插件默认禁用9.x 版本甚至直接移除了。所以这个方法能解决眼前问题但给未来埋了一个雷一旦某天你升级 MySQL 大版本这些老插件账号可能全部无法使用届时还是得改回新插件。代价之二是在多节点或云数据库场景下ALTER USER需要在所有相关节点上执行尤其是主从复制架构里改了主库账号认证方式后从库的复制账号可能不受影响但应用账号需要逐个调整。4.3 适合用这个方案的场景如果你遇到以下情况选方法二比方法一更合适你的某台客户端实在太老驱动版本停留在 5.x不支持caching_sha2_password公司 DBA 不给开放 JDBC URL 参数权限但允许你在数据库端做用户调整项目里使用了某些封装好的中间件你无法修改连接串。但反过来如果是新建项目、新环境我建议你优先用方法一。原因很直接你的目标是让应用适配 MySQL 8 新的安全体系而不是让 MySQL 8 倒退成 5.7 的样子。5. 方法三手动指定服务端 RSA 公钥中间路线这个方法比较冷门但它是官方提供的一种折中方案你不开启allowPublicKeyRetrievaltrue也不改账号插件而是手动把服务器 RSA 公钥配置到客户端让驱动直接使用它完成加密。5.1 如何获取服务器 RSA 公钥方法很简单登录 MySQL 命令行执行SHOW STATUS LIKE Caching_sha2_password_rsa_public_key;得到的结果是一长串 PEM 格式的公钥内容-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA... -----END PUBLIC KEY-----把这段内容保存成文件比如mysql_public_key.pem。然后在 JDBC URL 里指定它jdbc:mysql://localhost:3306/testdb?useSSLfalseserverRsaPublicKeyFile/path/to/mysql_public_key.pem连接器在握手时发现本地已经有可信的公钥就不再发起公钥检索请求直接拿这份公钥加密密码。这个方案本质上跟方法一解决的是同一个问题差别在于公钥的来源——一个是从服务器现取一个是本地事先存好。5.2 这个方法的真实定位你可以把方案三理解为“比方案一安全一点但比 SSL 省事一点”。公钥在本地文件里中间人可以替换传输中的公钥但没法替换你本地已经存在的公钥所以窃听的难度提高了。代价是你需要保证公钥文件与服务端的密钥对匹配如果哪天数据库实例重建、密钥对更换这个文件也得同步更新维护成本比方法一高。说实话这个方案在真实项目里用得不算多。它有价值的地方在于那些安全要求严、不允许开启allowPublicKeyRetrieval的环境里提供了一个合规的选项。另外某些老版本驱动比如 MySQL Connector/J 5.1.40 之前的版本可能不认识allowPublicKeyRetrieval参数但认识serverRsaPublicKeyFile你会碰到这种兼容性驱动的概率极低但知道有这条路总是好的。6. 方法四启用 SSL/TLS 安全连接根治方案从安全角度讲SSL 是唯一能够真正化解“公钥明文传输风险”的正道。因为一旦链路加密公钥的传递和密码的传输都发生在 TLS 隧道内部不存在明文暴露的问题allowPublicKeyRetrieval自然就没有存在的意义。这是方案四跟方案一在理念层面最本质的区别前者是“接受风险换便利”后者是“消除风险换安心”。6.1 零成本快速启用 SSL开发环境版MySQL 8 默认在数据目录里会生成自签名 SSL 证书你可以直接用它们开启 SSL不必自己买证书或配证书体系。JDBC URL 这样写jdbc:mysql://localhost:3306/testdb?useSSLtruerequireSSLtrueverifyServerCertificatefalse三个参数的含义分别拆开讲useSSLtrue启用 SSL 加密通道requireSSLtrue如果服务端不支持 SSL直接报错而不是静默降级verifyServerCertificatefalse不校验证书链因为自签名证书无法通过公共 CA 校验跳过这一步才能在开发环境跑通。配合这段连接串即使allowPublicKeyRetrieval保持默认 false连接也能成功因为 RSA 公钥会通过加密通道传给客户端驱动认为这个传递过程是安全的。6.2 生产环境 SSL 配置的合理姿势生产环境建议把verifyServerCertificate设为true同时把 CA 证书导入客户端的信任库。具体步骤比较繁琐不是本次重点但大方向是这样的从 MySQL 服务端复制ca.pem证书文件用keytool导入到 Java 的信任库JDBC URL 里增加trustCertificateKeyStoreUrl和trustCertificateKeyStorePassword参数。这种配置下SSL 连接既有加密性又有身份校验安全性最完整。代价是运维成本上来了——证书轮换、多环境分发、客户端信任库更新都是日常维护工作。6.3 SSL 方案和前面方案怎么选根据我的经验决策树可以这么走如果是本地开发、联调测试直接方法一加个参数搞定别折腾 SSL如果是公司内网生产环境且网络可控方法一也够但如果你司安全团队比较严格可以选方案三或方案四如果是公网或者跨网络环境直接方案四不开任何豁免参数。为了让你一目了然我把四个方案的横向对比列在下面。方案改动位置配置成本安全性适用场景长期影响方法一allowPublicKeyRetrievaltrue客户端连接串极低中等公钥明文传输开发、内网测试无参数级方法二改 mysql_native_password服务端账号极低较低SHA-1 已弃用老客户端兼容MySQL 8.4 默认禁用9.x 移除方法三serverRsaPublicKeyFile客户端证书文件中等中高公钥前置可信禁止自动检索的场景密钥更换需同步文件方法四SSL/TLS客户端连接串证书高高端口安全生产环境、公网链路证书生命周期管理7. 方法五升级或降级驱动版本治标但偶尔有效这个方法比较特殊它不是“正确解法”而更像一种事故现场处理手段。但既然标题说要写 5 种方法我就把它列进来同时把背后的兼容性逻辑讲透。7.1 驱动版本和 MySQL 服务端版本的兼容矩阵MySQL Connector/J 的版本号和 MySQL 服务端版本号从 8.0 开始是同步的。早期 8.0.x 驱动确实存在一些 bug比如某些版本在连接caching_sha2_password账号时即使加了allowPublicKeyRetrievaltrue也会抛错原因是对握手协议的实现不完整。如果你遇到这种情况优先把驱动升级到最新稳定版大概率能修复。反之如果你因为某些原因必须使用很老的驱动比如 5.1.x它会默认走mysql_native_password协议反而不会报公钥错误——但它也根本没法连caching_sha2_password账号除非服务端账号改成老插件。所以老驱动和老插件是配套的新驱动和新插件是配套的混搭才会出现各种奇奇怪怪的握手失败。7.2 实际操作建议在pom.xml里升级驱动版本dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency如果你用的 Maven 仓库已经切换到新坐标也可以写成dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId version8.3.0/version /dependency升级驱动之后重新测试很多人会发现同样连接串、同样账号错误不再出现因为新驱动对caching_sha2_password的握手实现已经非常成熟。7.3 这个方法什么时候排在前面说实话日常情况下我不会优先推荐升级驱动来解这个问题因为它不够“精准”。但如果你的驱动版本确实特别老或者你发现同样的代码在别人电脑上能连、自己电脑上报错那优先检查两边驱动的版本差异比改参数更合理。驱动版本不一致导致的异常行为在团队协作里很常见我用过的真实案例如下A同学本地是 8.0.29B同学本地是 8.0.11同样的连接串B同学就是连不上升级驱动后立刻恢复。8. 非 JDBC 场景的同类问题处理这个标题聚焦在 MySQL 8 JDBC但实际工作中你会遇到各种客户端报类似错误。我把常见工具和语言的解决方式一并列出来帮你少走弯路。8.1 Python 连接器mysql-connector-python里对应的参数叫allow_public_key_retrieval。示例import mysql.connector conn mysql.connector.connect( hostlocalhost, userroot, passwordyourpassword, databasetestdb, allow_public_key_retrievalTrue, use_pureTrue )use_pureTrue有时也被建议加上它会强制使用纯 Python 实现避免 C 扩展里一些不可控的握手问题。注意这里的参数名是下划线风格跟 Java 的驼峰命名不一样别抄串了。8.2 DBeaver / DataGripDBeaver 连接 MySQL 8 时如果报这个错在“编辑连接”-“驱动属性”里添加allowPublicKeyRetrieval trueDataGrip 同理在连接参数的“高级”选项卡里加同名属性。有些版本的 DataGrip 会在连接时自动带上这个参数如果没带手写补上即可。这类 GUI 工具的坑在于它们经常缓存连接配置改了参数之后要重新测试连接甚至重启客户端才能生效。8.3 Go / Node.js 等生态Go 的go-sql-driver/mysql支持在 DSN 里添加参数user:passtcp(localhost:3306)/dbname?allowPublicKeyRetrievaltrueNode.js 的mysql2驱动默认就能兼容caching_sha2_password正常情况下不会报这个错。如果你在 Node 里遇到检查是不是用了旧版mysql包换了mysql2就行。9. 高频问题排查清单与避坑笔记为了对得起“完全指南”这四个字我把平时见到的高频问题整理一下方便你对照排查。9.1 问题一加了参数还是报错先说结论如果你确定 URL 里的allowPublicKeyRetrievaltrue已经生效但报错依旧存在可能是以下原因之一参数拼写错误allowPublicKeyRetrieval中间是驼峰写成都小写allowpublickeyretrieval或拼成allowPublicKeyRetrival少一个 i驱动识别不到参数被连接池覆盖比如 HikariCP 配置了dataSourceProperties里面通过Properties设置连接属性时优先级比 URL 参数高如果那里没加这个参数URL 里加了也没用驱动版本太老确认你用的 Connector/J 是 8.0.x5.1.x 根本不认这个参数你连的是通过代理转发的服务端公钥检索请求被代理拦截连接器收到了非预期响应。这种情况在云数据库的堡垒机、数据库代理场景下比较容易出现需要检查代理层是否支持 MySQL 8 协议。9.2 问题二改了账号插件是不是一劳永逸从 MySQL 8.0 到 8.3是的。但从 8.4 开始不是。前面提过 MySQL 8.4 默认禁用mysql_native_password插件MySQL 9.x 直接移除。如果你现在的版本是 8.0.x用了方法二请把这个技术债记在排期里找一个版本升级窗口统一改回caching_sha2_password并配合allowPublicKeyRetrieval使用。我自己见过好几个团队在 8.4 升级时被这个雷炸到生产停机公告写得一度很紧张原因就是老账号全部认证失败。9.3 问题三Docker 里跑 MySQL 8 有这个区别吗Docker 镜像mysql:8默认配置和原生安装一致没有特殊逻辑。你只需要注意镜像内挂载的配置文件如果修改了default-authentication-plugin会影响新建用户默认使用的插件类型。比如你在 my.cnf 里设置了default-authentication-pluginmysql_native_password那么新建的用户默认就是老插件不会报公钥错误。但这样做的后果与问题二相同——你在刻意回到旧安全体系。9.4 问题四多个数据源同时连接一个报错一个正常这种情况多半是因为不同数据源用的数据库账号不同。可能你的应用里有两个账号一个是建库时默认创建的新插件另一个是历史遗留账号老插件于是同一个应用里出现了“一部分 SQL 正常一部分连接失败”的割裂现象。排查时逐个登录每个账号查plugin字段就能定位差异。10. 我的实操经验总结与最终建议处理过几十次这个报错之后我的习惯性做法已经固定下来了先看账号插件再看驱动版本最后改连接参数。这个顺序能最快定位问题避免盲目加参数。第一步进 MySQL 查账号插件。如果是caching_sha2_password继续下一步如果已经是mysql_native_password那报错前提不成立问题出在别处大概率是密码错误或网络不通被报错文案误导了。第二步确认驱动版本在 8.0.19 以上。低于这个版本的 Connector/J 对caching_sha2_password的兼容实现不够完善即便加了参数也可能有各种边缘问题。第三步给连接串追加allowPublicKeyRetrievaltrue重新测试。通过之后再评估这个连接所在的环境是否适合长期这么配。如果是不太信任的网络紧接着切换 SSL 方案。最后再说一个小技巧如果你在修改连接串后发现连接速度变慢了别急着怀疑参数先看是不是每次连接都触发了完整的 RSA 密钥交换。caching_sha2_password带了“caching”这个词意味着服务端会缓存认证结果同一个账号在短时间内重复认证可以走缓存路径速度会快很多。首次连接慢一点是正常的后续就快了。这几种方法没有绝对的“谁最好”只有“谁最适合你的场景”。把这套逻辑理清楚下次再看到Public Key Retrieval is not allowed你就不会再慌着复制网上的配置了——你已经知道它在说什么也知道每条路通向哪里。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →