现象:ERROR 2061 “requires secure connection”:根因是 caching_sha2_password 缓存未命中
现象
非 TLS 连接 MySQL 8.x,账号使用默认插件 caching_sha2_password,连接时报:
ERROR 2061 (HY000): Authentication plugin 'caching_sha2_password' reported error: Authentication requires secure connection.
同一条命令在别的机器、别的时间点又能正常连接。JDBC 下对应的报错是 Public Key Retrieval is not allowed,canal 之类的客户端下是 caching_sha2_password Auth failed。
协议定义参考 MySQL 官方协议文档 与 官方手册 8.4.1.2。
相关量的定义:
- Nonce = 20 字节随机数
- Scramble = XOR(SHA256(password), SHA256(SHA256(SHA256(password)), Nonce))
- Hash entry = account_name -> SHA256(SHA256(user_password))
authentication_string存的是 SHA256-crypt 5000 轮结果($A$前缀)
两阶段认证:fast 与 full
Fast authentication:服务端内存缓存里已有该账号的 hash entry,用客户端发来的 scramble 直接校验,通过即进入命令阶段。
Complete / full authentication:缓存没有条目或校验失败,服务端要求走完整认证——通过安全通道(TLS / Unix socket / shared memory),或用 RSA 加密把密码送上来。服务端用 mysql.user.authentication_string 校验,成功后把 hash entry 写入缓存。
包层面:握手后服务端可能回 AuthMoreData,status byte 为 0x01,其中:
0x03= fast_auth_success,紧随 OK 包0x04= perform_full_authentication,要求客户端继续发密码
另有 AuthSwitchRequest(0xfe + plugin name),用于握手时声明的插件与账号实际插件不一致的情况。
缓存失效时机
缓存由 sha2_cache_cleaner 审计插件维护,内置无需安装。key 是 user@host,纯内存,不落盘。
| 事件 | 说明 |
|---|---|
| 账号创建后首次连接 | 无 hash entry,必然 full auth |
改密(ALTER USER / SET PASSWORD) | 条目失效 |
RENAME USER | 条目失效 |
DROP USER | 条目失效 |
FLUSH PRIVILEGES | 清空全部账号缓存 |
| 服务端关闭 | 缓存不跨重启 |
以上事件之后,该账号的下一次连接必须使用安全连接或 RSA 密钥交换;成功之后重新进缓存,直到下一次失效事件。
由此产生的三个真实症状
- 手动登一次就好:运维先用带
--get-server-public-key的 mysql 客户端连一次,缓存里写入了 hash entry,业务连接随后命中 fast auth。 FLUSH PRIVILEGES后批量 2061:清空的是全部账号缓存,所有非 TLS 业务连接同时掉进 full auth。- 重启后首批连接失败:缓存不跨重启,重启后的头几条连接全部走 full auth。
非 TLS 下的 RSA 公钥路径
服务端默认不主动下发公钥,客户端没带公钥就报 2061。客户端选项有两种:
--get-server-public-key:向服务端索取,方便,但存在中间人替换公钥的风险--server-public-key-path=file:预先配置公钥文件,推荐
服务端侧变量:
caching_sha2_password_private_key_path/caching_sha2_password_public_key_pathcaching_sha2_password_auto_generate_rsa_keys默认开启自动生成,文件在 datadir 下private_key.pem/public_key.pem(社区文章里这两个名字常被写反)- 状态变量
Caching_sha2_password_rsa_public_key可读当前公钥
复制链路:CHANGE REPLICATION SOURCE TO SOURCE_PUBLIC_KEY_PATH='...' 或 GET_SOURCE_PUBLIC_KEY=1;MGR 在 group_replication_ssl_mode=DISABLED 时需要 group_replication_recovery_get_public_key=ON 或 group_replication_recovery_public_key_path。
各驱动差异
- JDBC Connector/J 8.x:
allowPublicKeyRetrieval默认false,非 TLS + 未缓存时报Public Key Retrieval is not allowed,需要显式打开。 - go-sql-driver/mysql 1.5.0+:已实现 caching_sha2_password 完整认证,非 TLS 时自动向服务端索取公钥,不需要 JDBC 那样的开关;也可以用
tls=true/preferred直接走 TLS 绕开 RSA。 - PHP PDO(mysqlnd):7.4+ 支持该插件。
- 自研协议客户端(各种 binlog 同步、代理、中间件):必须自己把 AuthMoreData 和后续 OK 包读完,没有库帮忙兜底。
自研协议客户端的包对齐坑
krowinski/php-mysql-replication PR #148:MySQL 8.4 + caching_sha2_password 账号下,switchAuth() 读完 auth-switch 响应后没有把 caching_sha2_password 多发的包抽干(AuthMoreData 0x01+0x03、随后 OK),导致后续每次 getResponse() 都提前两个包。第一个读到的包被当成 event 交给 Event::consume(),在 readInt32() / readUInt16() 上抛 TypeError(PHP 8 严格类型)。
修法是抽干这两个包,遇到 0x04 直接抛出明确异常而不是挂住,并给 consume() 加上小于 20 字节的最小包头保护。
另一个案例:alibaba/canal PR #4767。canal 1.1.6 + MySQL 8.0.30 只实现了 fast auth,没有实现 full auth 的公钥获取,所以「首次连接」或执行 FLUSH PRIVILEGES 之后稳定报 caching_sha2_password Auth failed;非首次连接因为命中缓存反而正常。PR 补上了 full auth 从服务端获取 public key 的流程。
排查顺序
- 看报错落在哪一类:2061 /
Public Key Retrieval is not allowed/caching_sha2_password Auth failed,都指向缓存未命中的 full auth。 - 确认连接方式:是否 TLS、是否 Unix socket、是否 shared memory。安全通道下不会触发 RSA 路径。
- 对照失效表:是不是刚新建账号、改密、
RENAME USER、FLUSH PRIVILEGES或刚重启。 - 命令行加
--get-server-public-key或--server-public-key-path;JDBC 加allowPublicKeyRetrieval=true;go-sql-driver 确认版本 ≥ 1.5.0。 - 自研客户端检查 AuthMoreData 与后续 OK 包是否抽干净,包偏移是否错位。
- 复制与 MGR 链路单独检查
SOURCE_PUBLIC_KEY_PATH/GET_SOURCE_PUBLIC_KEY/group_replication_recovery_get_public_key。
MySQL 8.0.4 起默认插件从 mysql_native_password 改为 caching_sha2_password,native 在 8.0.34 弃用、8.4 默认不加载、9.0 服务端移除,拿它兜底是死路。