问题现象

Java 应用在运行过程中突然报java.sql.SQLNonTransientConnectionException: Public Key Retrieval is not allowed错误。

开发童鞋表示不理解,毕竟应用没做任何变更,为什么会突然出现这个错误?

2025-03-31 08:31:11 - create connection SQLException, url: jdbc:mysql://10.0.1.86:3306/information_schema?useSSL=false, errorCode 0, state 08001
java.sql.SQLNonTransientConnectionException: Public Key Retrieval is not allowed
        at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:110)
        at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122)
        at com.mysql.cj.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:828)
        at com.mysql.cj.jdbc.ConnectionImpl.<init>(ConnectionImpl.java:448)
        at com.mysql.cj.jdbc.ConnectionImpl.getInstance(ConnectionImpl.java:241)
        at com.mysql.cj.jdbc.NonRegisteringDriver.connect(NonRegisteringDriver.java:198)
        at com.alibaba.druid.pool.DruidAbstractDataSource.createPhysicalConnection(DruidAbstractDataSource.java:1682)
        at com.alibaba.druid.pool.DruidAbstractDataSource.createPhysicalConnection(DruidAbstractDataSource.java:1803)
        at com.alibaba.druid.pool.DruidDataSource$CreateConnectionThread.run(DruidDataSource.java:2914)
        ...

问题原因

用户使用的密码认证插件是 caching_sha2_password 且 JDBC 连接串中指定了useSSL=false

当碰到以下场景,就会出现上述报错:

  1. 第一次连接时。
  2. 数据库实例发生了重启。
  3. 服务端执行了 FLUSH PRIVILEGES 操作。

解决方法

解决方法有以下几种:

方案 1

将用户的密码认证插件调整为 mysql_native_password。不推荐该方法,因为 MySQL 9.0 移除了 mysql_native_password。

ALTER USER 'u1'@'%' IDENTIFIED WITH mysql_native_password BY 'MySQL@2025';

方案 2

在 JDBC 连接串中设置useSSL=true。推荐该方法,但开启 SSL 会有一定的性能开销。

如果既不想开启 SSL,又想避免 Public Key Retrieval is not allowed 错误,以下是两个可选的解决方案:

方案 3

在 JDBC 连接串中添加allowPublicKeyRetrieval=true

该方法会自动从 MySQL 服务端获取 RSA 公钥,但这种方法有一定的安全风险,可能会受到中间人攻击。攻击者可以伪造 RSA 公钥,窃取用户密码。

方案 4

在 JDBC 连接串中指定serverRSAPublicKeyFile

该方法需要将方案 3 中的公钥内容写到应用侧的一个本地文件中,具体步骤如下:

  • 通过show status like 'Caching_sha2_password_rsa_public_key'命令或者从参数 caching_sha2_password_public_key_path(默认是 public_key.pem)指定的文件中获取公钥内容。

  • 将公钥内容保存到应用侧的一个本地文件中。

  • 在 JDBC 连接串中指定该公钥文件路径。如,

JDBC_URL = "jdbc:mysql://10.0.1.86:3306/information_schema?useSSL=false&serverRSAPublicKeyFile=/usr/local/caching_sha2_password_public_key.pem"

相较于方案 3,方案 4 无疑会更安全,但在高可用环境下并不适用,因为主从节点的公钥内容通常不同。一旦发生主从切换,JDBC 客户端在重新连接新的主节点时,就可能因公钥不一致而触发 Public Key Retrieval is not allowed 错误。除非在部署时显式配置,让主从节点使用相同的公钥,才能避免该问题。

四种方案的优缺点对比

方案 安全等级 适用场景 备注
SSL 加密连接 (useSSL=true) ★★★★★ 所有生产环境 最安全方案,推荐优先使用
手动配置 RSA 公钥 (serverRSAPublicKeyFile) ★★★★☆ 禁用 SSL 的生产环境 需维护公钥文件
自动获取 RSA 公钥 (allowPublicKeyRetrieval=true) ★★☆☆☆ 可信内网环境 存在中间人攻击风险
更改认证插件 ★☆☆☆☆ 不推荐 兼容性差,安全性低

根因分析

简单来说,caching_sha2_password 插件为了加快认证过程,在服务端维护了一个密码哈希缓存。当客户端发起连接时:

  • 如果用户的密码哈希已经被缓存,服务端可以直接验证,无需客户端发送明文密码进行验证。
  • 如果缓存中没有该用户的密码哈希(比如第一次连接时,除此之外,数据库重启或执行 FLUSH PRIVILEGES,还会清除密码哈希缓存),则客户端需要发送明文密码进行认证。

在发送明文密码时,出于安全考虑,MySQL 要求:

  • 要么客户端和服务端之间建立 SSL 加密连接。
  • 要么客户端允许通过服务端公钥加密明文密码。

如果两者都不满足,就会抛出 Public Key Retrieval is not allowed 错误。

caching_sha2_password 的认证交互流程

以下是客户端与服务端使用 caching_sha2_password 插件进行身份认证时的完整交互流程:

具体实现细节如下:

  1. MySQL 服务端收到客户端的请求后,会生成一个长度为 21 字节的随机数。

  2. MySQL 服务端给客户端发送一个握手包(handshake packet)。

    该握手包是 MySQL 服务端与客户端建立连接时发送的第一个数据包,包的内容如下:

    握手包的构造和发送是在send_server_handshake_packet函数中实现的。

    1. 协议版本。

    2. 服务端版本。

    3. 线程 ID。

    4. 随机数的第一部分。

    5. 服务端能力标志(低16位) 。

    6. 默认字符集编号。

    7. 服务端状态标志。

    8. 服务端能力标志(高16位) 。

    9. 随机数的的长度。

    10. 保留字段。

    11. 随机数的第二部分。

    12. 默认的密码认证插件名称。

      在 MySQL 8.4 之前,默认的密码认证插件由 default_authentication_plugin 参数决定。在 MySQL 5.7 中,该参数的默认值为 mysql_native_password,在 MySQL 8.0 中,该参数的默认值为 caching_sha2_password。在 MySQL 8.4 中,移除了这个参数,默认的密码认证插件硬编码为 caching_sha2_password。

  3. 客户端在接收到服务端的握手包后,会根据握手包中的密码认证插件进行身份验证。对于 caching_sha2_password 插件,客户端将基于以下公式构造一个 scramble_response,然后调用prep_client_reply_packet构造握手包发送给服务端。公式中的 random 是服务端在握手包中发送的随机数。

    scramble_response = XOR(SHA256(password), SHA256(SHA256(SHA256(password)) + random)
  4. 服务端调用parse_client_handshake_packet解析客户端返回的握手包,解析出来的内容包括用户名、密码(不是明文密码,是 scramble_response)、库名。

  5. 基于用户名和客户端 IP 从ACL缓存中找到用户对应的认证信息,包括密码认证插件和密码哈希值。

  6. 调用Caching_sha2_password::fast_authenticate进行快速验证。快速验证的逻辑如下:

    • 首先基于用户名和主机名构造一个 authorization_id。

    • 判断 authorization_id 是否在 m_cache 存在。m_cache 即密码哈希缓存,是 caching_sha2_password 中的关键组件,它的底层实现是一个哈希表。该哈希表的键是 authorization_id,值是一个二维数组,用来存储新旧两个密码哈希值(从 MySQL 8.0 开始,一个用户可以设置两个密码),存储的值是对密码进行两次 SHA-256 哈希计算的结果,即SHA256(SHA256(password)

    • 如果存在,则通过以下步骤验证密码是否正确。

      SHA2(m_known, rnd) => scramble_stage1
      XOR(scramble, scramble_stage1) => digest_stage1
      SHA2(digest_stage1) => digest_stage2
      m_known == digest_stage2

      其中,m_known 是 m_cache 中存储的密码哈希值,即 SHA256(SHA256(password),rnd 是服务端发送给客户端的随机数(random),scramble 是客户端返回给服务端的 scramble_response。

      最后,将 m_known 与 digest_stage2 进行比较,如果相等,则意味着密码正确。这个时候,服务端会给客户端发送一个fast_auth_success包。

    • 如果 authorization_id 在 m_cache 中不存在,或者存在但不匹配,则意味着快速验证失败,这个时候,服务端会给客户端发送一个perform_full_authentication包,要求客户端发送密码进行身份验证。

  7. 客户端收到服务端发送的perform_full_authentication包后:

    • 如果连接是安全的(即开启了 SSL),则会向客户端发送明文密码。

    • 如果连接不是安全的(即没有开启 SSL,一般是因为客户端显式指定了--ssl-mode=DISABLED),则会调用 rsa_init() 初始化 RSA 公钥,这个公钥是 mysql 客户端参数 server-public-key-path 指定的。如果没指定,则看 mysql 客户端参数中是否指定了get-server-public-key。

      如果既没指定 server-public-key-path,又没指定 get-server-public-key,则 mysql 客户端会提示ERROR 2061 (HY000): Authentication plugin 'caching_sha2_password' reported error: Authentication requires secure connection.错误。

      JDBC 中的处理逻辑类似,对于非 SSL 连接,如果既没指定 serverRSAPublicKeyFile,又没指定allowPublicKeyRetrieval,则会提示Public Key Retrieval is not allowed错误,具体的实现细节可参考 CachingSha2PasswordPlugin.java 中的 nextAuthenticationStep 方法。

      如果指定了 get-server-public-key,则客户端会向服务端发送一个request_public_key包。

  8. 服务端收到客户端发送的request_public_key包后,会将 RSA 公钥发送给客户端。

  9. 客户端收到 RSA 公钥后,首先会将密码和服务端之前发送的随机数进行异或运算,然后使用 RSA 公钥对异或后的结果进行加密,最后将加密后的密文发送给服务端。

  10. 服务端收到加密后的密文后,会调用 decrypt_RSA_private_key 进行解密,获取明文密码。

  11. 服务端 Caching_sha2_password::authenticate 验证密码是否正确。验证的逻辑如下:

    • 从 mysql.user 表 authentication_string 字段中提取迭代次数和盐值。

      对于 caching_sha2_password,authentication_string 的格式如下:

      分隔符[摘要类型]分隔符[迭代次数]分隔符[盐值][哈希摘要]

      其中,分隔符默认是 $,摘要类型是单个字母,A 表示使用 SHA256 算法,迭代次数是 3 位十六进制字符串,默认是 005,乘以 ITERATION_MULTIPLIER(默认是 1000),即为实际迭代次数(默认是 5000 次)。盐值是一个长度为 20 的随机字符串,剩下的字符串即为哈希摘要,长度为 43 字节。

    • 基于提取的迭代次数、盐值和明文密码生成一个哈希摘要。

    • 判断提取的哈希摘要和生成的哈希摘要是否相等,如果相等,则意味着密码正确,否则是错误的。

参考资料

  1. Protocol::HandshakeV10:https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_packets_protocol_handshake_v10.html

  2. Caching_sha2_password information:https://dev.mysql.com/doc/dev/mysql-server/latest/page_caching_sha2_authentication_exchanges.html

  3. WL#9591: Caching sha2 authentication plugin:https://dev.mysql.com/worklog/task/?id=9591

Java应用出现 Public Key Retrieval is not allowed 报错的常见原因和解决方法的更多相关文章

  1. mysql遇到java.sql.SQLNonTransientConnectionException: Public Key Retrieval is not allowed

    在连接数据库的url中,加上allowPublicKeyRetrieval=true from:https://blog.csdn.net/Gushiyuta/article/details/9323 ...

  2. 关于JDBC连接数据库时出现的Public Key Retrieval is not allowed错误

    问题描述 最近在学习MyBatis框架,参考官方的文档通过配置文件的方式已经实现了通过Configuration配置文件和mapper映射文件访问mysql8数据库,于是想试试不使用XML文件去构建S ...

  3. Jdbc连接MySQL 8时报错“MySQLNonTransientConnectionException: Public Key Retrieval is not allowed”

    一.问题 因停电检修,今天重启服务器后,再启动jboss就报错"MySQLNonTransientConnectionException: Public Key Retrieval is n ...

  4. JDBC链接数据库MySQL 8.0 Public Key Retrieval is not allowed 错误的解决方法

    现象 Mybatis和Spring框架整合过程中报 com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: Publ ...

  5. MySQL连接提示 public key retrieval is not allowed

    使用DBeaver连接mysql数据提示public key retrieval is not allowed 修改DBeaver的驱动属性中的allowPublicKeyRetrieval

  6. Public Key Retrieval is not allowed

    链接MySQL数据库报错: 数据库连接url中添加对应属性的支持.allowPublicKeyRetrieval=true&useSSL=false url: jdbc:mysql://loc ...

  7. spring boot MySQL Public Key Retrieval is not allowed

    建议在链接url处添加对应的属性 jdbc:mysql://localhost:3306/book?allowPublicKeyRetrieval=true&useSSL=false 

  8. Nacos使用 MySQL 8.0 提示Public Key Retrieval is not allowed

    原因如下(参考官网给出的连接选项): 如果用户使用了 sha256_password 认证,密码在传输过程中必须使用 TLS 协议保护,但是如果 RSA 公钥不可用,可以使用服务器提供的公钥:可以在连 ...

  9. Mysql8- Public Key Retrieval is not allowed

    在使用 MySQL 8.0 时重启应用后提示 com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: Public ...

  10. Mysql8.0 Public Key Retrieval is not allow错误的解决办法

    在使用Mysql 8.0时重启后启动项目的事后会报错com.mysql.jdbc.exceptions.jdbc4.MysqlNonTransientConnectionException: Publ ...

随机推荐

  1. Deepseek官网太卡,教你白嫖阿里云的Deepseek-R1满血版

    Deepseek卡的问题已经有几周了,还有同学没有解决这个问题? 今天就来教大家如何白嫖阿里云的Deepseek-R1满血版,新用户享受100万token额度,相当的富裕,可以随便用了.并且还配用AP ...

  2. CF57C Array 题解

    发现单调不降序列反过来就是单调不增序列,只需考虑单调不降序列即可. 假如将问题转化为:初始为 \(1\),一共有 \(n+1\) 个位置,有 \(n-1\) 次增加答案的机会,每个位置可以拥有多次增加 ...

  3. DW - 问题

    数据库三范式 1NF(First Normal Form):一个关系模式符合 1NF 的定义,则该关系模式是简单的.简单的意思就是不存在从属或重复的属性,即每个属性都是原子性的. 2NF(Second ...

  4. FormCreate中在事件中获取api

    form-create中在事件中获取api FormCreate 是一个可以通过 JSON 生成具有动态渲染.数据收集.验证和提交功能的表单生成组件.支持5个UI框架,并且支持生成任何 Vue 组件. ...

  5. 使用电阻网络实现的vga驱动电路,fpga驱动vga显示器验证,代替gm7123芯片

    之前驱动vga,要么是直接使用fpga管脚直接驱动,颜色为8原色 使用线缆 vs,hs,r,g,b一共五根线,三原色要么是0要么是1,所以色彩最多8种,rgb组合 若要实现真彩色驱动,如rgb888, ...

  6. Netty基础—5.Netty的使用简介

    大纲 1.Netty服务端的启动流程 2.服务端IO事件的处理类 3.Netty客户端的启动流程 4.客户端IO事件的处理类 5.启动Netty服务端和客户端的方法说明 6.Netty服务端和客户端使 ...

  7. k8s node节点报错 dial tcp 127.0.0.1:8080: connect: connection refused

    前言 在搭建好 kubernetes 环境后,master 节点拥有 control-plane 权限,可以正常使用 kubectl. 但其他 node 节点无法使用 kubectl 命令,即使同步过 ...

  8. 昨晚接收的俄罗斯Meteor-M2气象卫星云图,接收质量还可以!

    接收设备: 天馈:自制四臂螺旋天线 硬件:SDRsharp 跟踪:Orbitron.SDRSharpDriverDDE 频率:137.1MHZ 解码:SDRSharp.QPSK.M2_LRPT_Dec ...

  9. 如何不购买域名在云服务器上搭建HTTPS服务

    step 1: 事前准备 step 1.1: 云服务器 购买一台云服务器(带有弹性公网IP),阿里云,腾讯云,华为云什么的都可以. 选择ubuntu系统 开放安全组策略(把你需要的协议/端口暴露出来) ...

  10. Linux如何从命令行卡死的进程中退出?

    Linux如何从命令行卡死的进程中退出? 不知道大家在使用Linux的时候,会不会遇到一些命令,有可能卡顿,有可能执行时间过长(比如使用 find 查找某个文件),这个时候我不想继续执行这个命令了,说 ...