Java应用出现 Public Key Retrieval is not allowed 报错的常见原因和解决方法

问题现象

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,还会清除密码哈希缓存),则客户端需要发送明文密码进行认证。

在发送明文