1. 问题现象与背景分析
最近在排查一个Java应用连接MySQL数据库的报错时,遇到了"Public Key Retrieval is not allowed"这个错误。这个错误通常发生在使用MySQL 8.0及以上版本时,特别是当客户端使用JDBC连接且未正确配置SSL参数的情况下。
错误信息如下:
code复制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)
这个问题的根源在于MySQL 8.0开始默认使用了新的认证插件caching_sha2_password,取代了之前的mysql_native_password插件。新插件在安全性上有显著提升,但也带来了新的配置要求。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 问题原因深度解析
2.1 caching_sha2_password插件工作机制
caching_sha2_password是MySQL 8.0引入的新认证插件,它的工作流程可以分为以下几个阶段:
-
快速认证阶段:服务端会缓存用户的密码哈希值。如果缓存命中,客户端只需发送经过处理的密码响应即可完成认证。
-
完整认证阶段:当缓存未命中时(如首次连接、服务端重启或执行了FLUSH PRIVILEGES),客户端需要发送明文密码进行认证。此时:
- 如果连接启用了SSL加密,则直接发送明文密码
- 如果未启用SSL,则需要使用服务端的RSA公钥加密密码
2.2 错误触发条件
当以下条件同时满足时,就会触发"Public Key Retrieval is not allowed"错误:
- 使用caching_sha2_password认证插件
- JDBC连接字符串中设置了useSSL=false
- 需要完整认证(缓存未命中)
- 未配置允许公钥检索或未指定RSA公钥文件
3. 解决方案与配置方法
3.1 方案一:启用SSL加密(推荐)
在JDBC连接字符串中添加useSSL=true参数:
code复制jdbc:mysql://localhost:3306/dbname?useSSL=true
优点:
- 安全性最高
- 符合MySQL 8.0的安全设计理念
缺点:
- 会有一定的性能开销
- 需要配置SSL证书
3.2 方案二:允许公钥检索
在JDBC连接字符串中添加allowPublicKeyRetrieval=true参数:
code复制jdbc:mysql://localhost:3306/dbname?useSSL=false&allowPublicKeyRetrieval=true
优点:
- 配置简单
- 不需要SSL证书
缺点:
- 存在中间人攻击风险
- 不适合生产环境
3.3 方案三:指定RSA公钥文件
-
从MySQL服务端获取公钥:
sql复制SHOW STATUS LIKE 'Caching_sha2_password_rsa_public_key'; -
将公钥保存到客户端文件系统中
-
在JDBC连接字符串中指定公钥文件路径:
code复制jdbc:mysql://localhost:3306/dbname?useSSL=false&serverRSAPublicKeyFile=/path/to/public_key.pem
优点:
- 比方案二更安全
- 不需要启用SSL
缺点:
- 维护公钥文件较麻烦
- 在高可用环境中可能有问题(主从公钥不同)
3.4 方案四:改用mysql_native_password插件
将用户认证方式改为旧版插件:
sql复制ALTER USER 'username'@'host' IDENTIFIED WITH mysql_native_password BY 'password';
优点:
- 兼容性好
- 不需要额外配置
缺点:
- 安全性较低
- MySQL未来版本可能移除该插件
4. 各方案对比与选型建议
| 方案 | 安全性 | 复杂度 | 适用场景 |
|---|---|---|---|
| 启用SSL | ★★★★★ | 中 | 生产环境首选 |
| 指定RSA公钥文件 | ★★★★☆ | 高 | 禁用SSL的生产环境 |
| 允许公钥检索 | ★★☆☆☆ | 低 | 测试/开发环境 |
| 改用旧插件 | ★☆☆☆☆ | 低 | 兼容性要求高的旧系统 |
生产环境建议:优先考虑启用SSL加密。如果确实不能使用SSL,则考虑指定RSA公钥文件方案。
开发环境建议:可以使用允许公钥检索的方案,但要注意安全风险。
5. 实际配置示例
5.1 Spring Boot配置示例
在application.properties中:
code复制spring.datasource.url=jdbc:mysql://localhost:3306/mydb?useSSL=true&serverTimezone=UTC
spring.datasource.username=user
spring.datasource.password=pass
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
5.2 原生JDBC配置示例
java复制String url = "jdbc:mysql://localhost:3306/mydb?useSSL=true";
String user = "user";
String password = "pass";
try (Connection conn = DriverManager.getConnection(url, user, password)) {
// 使用连接
}
5.3 连接池配置示例(以HikariCP为例)
java复制HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/mydb?useSSL=true");
config.setUsername("user");
config.setPassword("pass");
HikariDataSource ds = new HikariDataSource(config);
6. 常见问题排查
6.1 启用了SSL但仍然报错
可能原因:
- SSL证书配置不正确
- 客户端不信任服务端证书
解决方案:
- 检查MySQL的SSL证书配置
- 在客户端添加信任存储:
code复制jdbc:mysql://host:3306/db?useSSL=true&trustCertificateKeyStoreUrl=file:/path/to/truststore&trustCertificateKeyStorePassword=password
6.2 公钥检索不工作
可能原因:
- 服务端未生成RSA密钥对
- 公钥文件路径不正确
解决方案:
- 检查MySQL的RSA密钥配置:
sql复制SHOW VARIABLES LIKE 'caching_sha2_password%'; - 确保公钥文件路径正确且可读
6.3 高可用环境下的问题
在主从切换时,如果主从节点的RSA公钥不同,使用serverRSAPublicKeyFile方案可能会失败。
解决方案:
- 确保主从使用相同的RSA公钥
- 或者改用SSL方案
7. 安全最佳实践
-
生产环境必须使用SSL:这是最安全的选择,可以有效防止密码被窃取。
-
定期轮换RSA密钥:如果使用RSA公钥方案,应定期更换密钥。
-
限制公钥检索:allowPublicKeyRetrieval=true只应在测试环境使用。
-
监控认证失败日志:关注认证失败的情况,及时发现潜在攻击。
-
保持客户端更新:使用最新版本的MySQL Connector/J,修复已知安全问题。
8. 性能考量
-
SSL性能影响:启用SSL会增加约10-15%的CPU开销,但现代服务器通常可以轻松处理。
-
连接池配置:合理配置连接池大小,减少SSL握手开销:
- 初始连接数:根据预期负载设置
- 最大连接数:避免设置过大
-
会话重用:启用SSL会话重用可以减少握手开销:
code复制jdbc:mysql://host:3306/db?useSSL=true&useSSLProtocol=TLSv1.2&enabledTLSProtocols=TLSv1.2&sessionVariables=ssl_session_cache_mode=ON
9. 版本兼容性说明
-
MySQL 8.0+:默认使用caching_sha2_password,需要注意本文讨论的问题。
-
MySQL 5.7:默认使用mysql_native_password,通常不会遇到此问题。
-
Connector/J版本:
- 8.0+:完全支持caching_sha2_password
- 5.1.x:需要升级才能支持新认证插件
10. 故障排查步骤
当遇到"Public Key Retrieval is not allowed"错误时,可以按照以下步骤排查:
-
确认MySQL版本和认证插件:
sql复制SELECT user, host, plugin FROM mysql.user; -
检查当前连接参数:
- 确认useSSL参数设置
- 检查是否有allowPublicKeyRetrieval或serverRSAPublicKeyFile参数
-
测试不同连接方式:
- 尝试启用SSL
- 尝试允许公钥检索
- 尝试指定公钥文件
-
检查网络环境:
- 确保网络连接正常
- 检查防火墙设置
-
查看MySQL错误日志:
- 定位认证失败的具体原因
11. 高级配置选项
11.1 自定义SSL参数
对于需要更高安全性的场景,可以自定义SSL参数:
code复制jdbc:mysql://host:3306/db?useSSL=true
&enabledTLSProtocols=TLSv1.2,TLSv1.3
&trustCertificateKeyStoreUrl=file:/path/to/truststore.jks
&trustCertificateKeyStorePassword=password
&clientCertificateKeyStoreUrl=file:/path/to/keystore.jks
&clientCertificateKeyStorePassword=password
11.2 性能调优参数
优化SSL性能的参数:
code复制jdbc:mysql://host:3306/db?useSSL=true
&sessionVariables=ssl_session_cache_mode=ON
&sslSessionCacheSize=100
&sslSessionTimeout=300
11.3 认证插件强制指定
强制使用特定认证插件:
code复制jdbc:mysql://host:3306/db?useSSL=true
&authenticationPlugins=com.mysql.cj.protocol.a.authentication.CachingSha2PasswordPlugin
12. 实际案例分享
最近处理的一个生产案例:某系统升级到MySQL 8.0后,夜间批处理作业频繁失败。错误日志显示"Public Key Retrieval is not allowed"。
问题分析:
- 批处理使用连接池,长时间空闲后连接失效
- 尝试重新连接时触发完整认证
- JDBC配置了useSSL=false且未允许公钥检索
解决方案:
- 在连接字符串中添加allowPublicKeyRetrieval=true(临时方案)
- 后续迁移到SSL方案,配置正确的证书
经验教训:
- 升级MySQL版本时要全面测试连接场景
- 长连接应用要特别注意认证方式的变化
- 临时方案要有明确的回滚计划
13. 开发环境快速解决方案
对于开发测试环境,可以快速使用以下配置:
code复制jdbc:mysql://localhost:3306/testdb?useSSL=false&allowPublicKeyRetrieval=true
但务必注意:
- 不要将此配置用于生产环境
- 在代码中添加明显注释,提醒后续修改
- 考虑在配置中心标记为"仅开发使用"
14. 连接池特殊配置
使用连接池时,需要注意:
-
Druid配置示例:
xml复制<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="url" value="jdbc:mysql://host:3306/db?useSSL=true"/> <property name="username" value="user"/> <property name="password" value="pass"/> <property name="connectionInitSqls"> <list> <value>SET SESSION sql_mode='STRICT_TRANS_TABLES'</value> </list> </property> </bean> -
HikariCP注意事项:
- 验证连接的有效性SQL要简单高效
- 考虑SSL握手时间,适当增加连接超时设置
15. 容器化环境特别考虑
在Kubernetes等容器环境中:
-
证书管理:
- 使用Secret存储SSL证书
- 通过volume挂载到容器
-
连接字符串配置:
yaml复制env: - name: JDBC_URL value: "jdbc:mysql://mysql-service:3306/db?useSSL=true&trustCertificateKeyStoreUrl=file:/certs/truststore.jks&trustCertificateKeyStorePassword=$(TRUSTSTORE_PASSWORD)" -
Sidecar模式:
- 考虑使用service mesh管理SSL
- 应用侧可以简化SSL配置
16. 多数据源配置建议
系统使用多个MySQL数据源时:
- 统一SSL策略:所有数据源应采用相同的安全策略
- 差异化配置:通过profile区分不同环境的参数
- 连接池隔离:不同重要级别的数据源使用独立的连接池
示例配置:
java复制@Configuration
@Profile("prod")
public class ProdDataSourceConfig {
@Bean
public DataSource primaryDataSource() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://primary:3306/db?useSSL=true");
// 其他生产环境专用配置
return new HikariDataSource(config);
}
}
17. 监控与告警设置
为确保连接安全稳定,建议设置:
-
认证失败监控:
- 监控"Public Key Retrieval is not allowed"错误频率
- 设置适当的告警阈值
-
SSL连接监控:
- 检查SSL连接占比
- 监控SSL握手失败情况
-
连接池健康检查:
- 活跃连接数
- 等待获取连接的线程数
- 连接创建时间
18. 迁移路径建议
从旧版本迁移到MySQL 8.0的建议步骤:
-
评估阶段:
- 识别所有连接MySQL的应用
- 检查当前连接字符串配置
-
测试阶段:
- 在测试环境验证各种连接场景
- 评估SSL性能影响
-
实施阶段:
- 分批次更新应用配置
- 准备回滚方案
-
验证阶段:
- 监控生产环境连接情况
- 收集性能指标
19. 相关参数参考
MySQL服务端相关参数:
| 参数名 | 默认值 | 说明 |
|---|---|---|
| default_authentication_plugin | caching_sha2_password | 默认认证插件 |
| caching_sha2_password_auto_generate_rsa_keys | ON | 自动生成RSA密钥 |
| caching_sha2_password_private_key_path | private_key.pem | 私钥路径 |
| caching_sha2_password_public_key_path | public_key.pem | 公钥路径 |
JDBC客户端相关参数:
| 参数名 | 说明 |
|---|---|
| useSSL | 是否启用SSL |
| allowPublicKeyRetrieval | 是否允许公钥检索 |
| serverRSAPublicKeyFile | RSA公钥文件路径 |
| sslMode | SSL模式(PREFERRED,REQUIRED,VERIFY_CA等) |
20. 总结与个人建议
经过多次处理这类问题的经验,我的建议是:
-
新项目:从一开始就使用SSL加密连接,避免后续迁移成本。
-
存量系统升级:
- 先评估影响范围
- 在测试环境充分验证
- 制定分阶段实施计划
-
开发规范:
- 在项目模板中预设安全的连接参数
- 代码审查时检查数据库连接配置
- 文档中明确记录安全要求
-
长期维护:
- 定期检查连接配置
- 关注MySQL版本更新说明
- 及时更新客户端驱动
在实际操作中,我发现很多团队因为怕麻烦而选择简单的allowPublicKeyRetrieval=true方案,这其实埋下了安全隐患。正确的做法是花时间配置好SSL,虽然初期投入较大,但从长远看更安全、更稳定。
