背景
近期,我们刚刚发布 Halo 2.23.2,就接到用户的反馈,称升级至 2.23.2 之后无法连接 MySQL 5.7 数据库,并在 SSL 握手阶段出现 handshake_failure 错误,降级到 2.23.1 及之前的版本、配置 MySQL 数据库驱动参数 sslMode=disabled或者升级至 MySQL 8 之后即可解决。
本地环境
❯ java -version
openjdk version "21.0.7" 2025-04-15 LTS
IBM Semeru Runtime Open Edition 21.0.7.0-m2 (build 21.0.7+5-LTS)
Eclipse OpenJ9 VM 21.0.7.0-m2 (build v0.51.0-release-3dcfa8b60d, JRE 21 Linux amd64-64-Bit Compressed References 20250415_432 (JIT enabled, AOT enabled)
OpenJ9 - 3dcfa8b60d
OMR - 9bcff94a2
JCL - e71bbea0566 based on jdk-21.0.7+5)注意这里的 Java 版本,它就是罪魁祸首。
尝试复现
遇到这种问题,我的本能反应就是尝试在本地进行复现。于是我在本地启动 MySQL 5.7,用源码启动 Halo,但是无论我如何配置都无法复现,甚至为了避免 MySQL 驱动自动禁用 SSL 连接,R2DBC URL 的主机名我都没有直接用 IP 地址。
这里通过源码调试的方式,可以确认 MySQL 数据库驱动侧
sslMode配置为PREFERRED,符合预期。
我开始怀疑 1Panel 安装的 MySQL 5.7 有什么特殊的配置,下载好对应的配置,重建 MySQL 环境,最终的结果依然是无法复现。
这里我开始自我怀疑了,跳出问题复现的路线,转去 GitHub 上对比 Halo 2.23.1 和 Halo 2.23.2 两个版本之间到底修改了哪些内容,到后面才发现这一步疑似徒劳。浏览完文件修改之后,我怀疑可能是因为升级了 Spring Boot 4.0.4 导致的问题,例如该版本可能升级了 MySQL 数据库驱动。不过浏览完了 Spring Boot 4.0.4 的发版日志,发现该版本并未升级 MySQL 数据库驱动。我还排查过可能会影响到数据库连接的上游项目,例如 Spring Data Relational,不过并未找到可疑的蛛丝马迹。
问题再现
此时我消耗了大量的精力,大致确认上游大概率没问题之后,又回到主线任务——问题复现。启动好本地的 1Panel 环境,并安装了 MySQL 5.7,尝试用源码启动 Halo 并连接到该数据库,问题依旧无法复现。这时我严格按照用户的环境,在 1Panel 侧通过应用的方式直接安装了 Halo 2.23.2,成功复现了用户遇到的问题。
离真相更进一步
通过以上的成功复现加上多次失败的复现的对比,只有通过容器部署的 Halo 2.23.2 才能够复现,通过 Jar 包 以及源码启动均无法复现,于是开始怀疑容器内的环境导致无法和 MySQL 实例 SSL 握手。
既然能够复现,那就好办了。随后,我通过两种方式运行时都设置了对应模块的日志级别为调试,对比日志输出后发现了一些关键日志,两种案例的关键日志片段如下所示:
连接失败案例的关键日志片段:
2026-04-18T02:09:52.039Z DEBUG 1 --- [tor-tcp-epoll-2] io.netty.handler.ssl.OpenSsl : netty-tcnative not in the classpath; OpenSslEngine will be unavailable. 2026-04-18T02:09:52.118Z DEBUG 1 --- [tor-tcp-epoll-2] io.netty.handler.ssl.JdkSslContext : Default protocols (JDK): [TLSv1.3, TLSv1.2] 2026-04-18T02:09:52.119Z DEBUG 1 --- [tor-tcp-epoll-2] io.netty.handler.ssl.JdkSslContext : Default cipher suites (JDK): [TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384] 2026-04-18T02:09:52.173Z DEBUG 1 --- [tor-tcp-epoll-5] i.n.resolver.dns.DnsNameResolverBuilder : resolveCache and TTLs are mutually exclusive. TTLs are ignored. 2026-04-18T02:09:52.174Z DEBUG 1 --- [tor-tcp-epoll-5] i.n.resolver.dns.DnsNameResolverBuilder : cnameCache and TTLs are mutually exclusive. TTLs are ignored. 2026-04-18T02:09:52.174Z WARN 1 --- [tor-tcp-epoll-2] i.a.r.mysql.client.ReactorNettyClient : Connection unexpectedly closed
连接成功案例的关键日志片段:
2026-04-18T10:10:51.809+08:00 DEBUG 1247627 --- [tor-tcp-epoll-2] io.netty.handler.ssl.OpenSsl : netty-tcnative not in the classpath; OpenSslEngine will be unavailable. 2026-04-18T10:10:51.851+08:00 DEBUG 1247627 --- [tor-tcp-epoll-2] io.netty.handler.ssl.JdkSslContext : Default protocols (JDK): [TLSv1.3, TLSv1.2] 2026-04-18T10:10:51.851+08:00 DEBUG 1247627 --- [tor-tcp-epoll-2] io.netty.handler.ssl.JdkSslContext : Default cipher suites (JDK): [TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_AES_128_GCM_SHA256, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA, TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384] 2026-04-18T10:10:51.886+08:00 DEBUG 1247627 --- [tor-tcp-epoll-2] i.n.h.s.u.InsecureTrustManagerFactory : Accepting a server certificate: CN=MySQL_Server_5.7.44_Auto_Generated_Server_Certificate 2026-04-18T10:10:51.899+08:00 DEBUG 1247627 --- [tor-tcp-epoll-2] io.netty.handler.ssl.SslHandler : [id: 0x5ec1cc53, L:/127.0.0.1:60664 - R:localhost.localdomain/127.0.0.1:3306] HANDSHAKEN: protocol:TLSv1.2 cipher suite:TLS_RSA_WITH_AES_128_GCM_SHA256 2026-04-18T10:10:52.020+08:00 DEBUG 1247627 --- [tor-tcp-epoll-5] i.n.resolver.dns.DnsNameResolverBuilder : resolveCache and TTLs are mutually exclusive. TTLs are ignored. 2026-04-18T10:10:52.020+08:00 DEBUG 1247627 --- [tor-tcp-epoll-5] i.n.resolver.dns.DnsNameResolverBuilder : cnameCache and TTLs are mutually exclusive. TTLs are ignored. 2026-04-18T10:10:52.020+08:00 DEBUG 1247627 --- [tor-tcp-epoll-5] i.n.resolver.dns.DnsNameResolverBuilder : authoritativeDnsServerCache and TTLs are mutually exclusive. TTLs are ignored.
在连接成功案例关键日志片段中,Halo 实例和 MySQL 实例在 TLS/SSL 握手阶段所使用的算法是 TLS_RSA_WITH_AES_128_GCM_SHA256,且该算法出现在了 JDK 支持列表中,而在连接失败案例关键日志片段中 JDK 支持的密码套件中并未包含,甚至连一个 TLS_RSA_开头的算法都没有。查询了 MySQL 官方文档才知道 MySQL 5.7 在进行 SSL 握手时正是使用了这类弱安全性的算法。
离真相更近了一步。我将怀疑的目标转移到了 Java 的安全配置身上。按葫芦画瓢,我对比了一下两个环境下的 Java 安全配置,该配置文件位于 $JAVA_HOME/conf/security/java.security,关于 TLS 的关键配置区别如下所示:
连接失败案例的关键配置片段:
jdk.tls.disabledAlgorithms=SSLv3, TLSv1, TLSv1.1, DTLSv1.0, RC4, DES, \
MD5withRSA, DH keySize < 1024, EC keySize < 224, 3DES_EDE_CBC, anon, NULL, \
ECDH, TLS_RSA_*, rsa_pkcs1_sha1 usage HandshakeSignature, \
ecdsa_sha1 usage HandshakeSignature, dsa_sha1 usage HandshakeSignature连接成功案例的关键配置片段:
jdk.tls.disabledAlgorithms=SSLv3, TLSv1, TLSv1.1, DTLSv1.0, RC4, DES, \
MD5withRSA, DH keySize < 1024, EC keySize < 224, 3DES_EDE_CBC, anon, NULL, \
ECDH在连接失败案例关键配置片段中有一个非常关键的配置 TLS_RSA_*,于是乎我删除了这个配置之后,并重启 Halo 容器,问题最终得以解决。到这里你以为就结束了吗?不,我们还不知道为什么容器内关于 Java 的安全配置为何会发生变化,况且 Halo 2.23.1 和 Halo 2.23.2 都是使用的同一个基础镜像 ibm-semeru-runtimes:open-21-jre,再加上我在本地未能复现。
再次尝试复现
我尝试在当前开发环境的 Java 安全配置 jdk.tls.disabledAlgorithms 中新增 TLS_RSA_*,重启 Halo 实例之后惊奇地发现仍然可以连接成功,并且在 JDK 支持算法列表中仍然有 TLS_RSA_开头的算法,连接所使用的算法仍然是 TLS_RSA_WITH_AES_128_GCM_SHA256。接着,我尝试在配置中新增了 TLS_RSA_WITH_AES_128_GCM_SHA256 ,竟然还能连接成功,不过这次连接的算法变成了另外的 TLS_RSA 开头的算法。最后我新增了所有的 TLS_RSA_ 开头的算法,才成功在本地环境下通过非容器部署的方式复现了。这时我仍然不知道具体原因。
寻得真相
Halo 容器所使用的基础镜像版本(open-21-jre)虽然没有变化过,但不意味着我们就固定了 Java 版本,这跟 latest 镜像一个原理。随着 OpenJDK 不断更新,该基础镜像也会持续迭代。
最后,逐个查看 OpenJDK 发版日志发现,他们从 OpenJDK 2.0.10 开始禁用了 TLS_RSA_ 开头的弱 TLS/SSL 算法,并且之后的版本才支持 TLS_RSA_* 通配符的解析,而我本地的 Java 版本却是 21.0.7 。不出意外,升级至 OpenJDK 21.0.10 之后依然能够复现以上问题。