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。
当碰到以下场景,就会出现上述报错:
- 第一次连接时。
- 数据库实例发生了重启。
- 服务端执行了 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,还会清除密码哈希缓存),则客户端需要发送明文密码进行认证。
在发送明文