环境:Ubuntu 24.04 LTS | OpenJDK 1.8.0_492 | Hutool 5.3.x | 阿里企业邮箱(smtp.mxhichina.com:587)
一、踩坑现场
某天,使用Hutool的MailUtil.send()发送邮件时,控制台无情地抛出了:
cn.hutool.extra.mail.MailException: MessagingException: Could not connect to SMTP host: smtp.mxhichina.com, port: 587 Caused by: javax.net.ssl.SSLHandshakeException: No appropriate protocol (protocol is disabled or cipher suites are inappropriate)邮件服务器是阿里企业邮箱,端口 587,开启 STARTTLS。按说配置无误,为何握手失败?
二、问题根源
JDK 1.8.0_492 的java.security文件中,jdk.tls.disabledAlgorithms默认禁用了TLSv1和TLSv1.1(仅保留 TLSv1.2 及以上)。而部分邮件服务商(尤其是老实例)可能只支持 TLSv1.0 或 TLSv1.1,双方无法协商出共同协议,于是抛出SSLHandshakeException。
三、解决方案概览
我尝试了多种手段,最终采用方法二(修改 java.security)成功。但为了给读者更多选择,这里整理了六种方案,按推荐度排序:
| 方案 | 核心操作 | 适用场景 | 风险 |
|---|---|---|---|
| ① 代码指定 TLS 协议 | 通过MailAccount设置mail.smtp.ssl.protocols | 大部分场景,最推荐 | 低 |
② 修改java.security | 移除jdk.tls.disabledAlgorithms中的 TLSv1/TLSv1.1 | JVM 全局生效,本文采用 | 中(影响所有 Java 应用) |
| ③ 检查 STARTTLS 配置 | 确保端口 587 开启 STARTTLS,关闭 SSL 直连 | 配置遗漏导致的连接失败 | 无 |
| ④ 改用 SSL 直连(端口 465) | 切换端口并启用sslEnable | 服务商支持 465 端口 | 无 |
| ⑤ 跳过证书验证 | 设置MailSSLSocketFactory信任所有主机 | 仅限开发测试 | 极高(生产禁用) |
| ⑥ 升级依赖版本 | 升级hutool-all和javax.mail | 旧版本无配置属性或Bug时 | 低 |
下面逐一详解。
四、方案详解
✅ 方案一:代码中指定 TLS 协议版本(首选)
此方法侵入性最小,无需修改 JVM,控制粒度细。在MailAccount中直接指定允许的协议列表:
MailAccountaccount=newMailAccount();account.setHost("smtp.mxhichina.com");account.setPort(587);account.setAuth(true);account.setUser("your@email.com");account.setPass("your-password");// 核心:启用 STARTTLSaccount.setStarttlsEnable(true);account.setSslEnable(false);// 指定可用的 TLS 版本(按优先级顺序)account.setCustomProperty("mail.smtp.ssl.protocols","TLSv1.2 TLSv1.1 TLSv1");// 如果 Hutool 版本较旧,可能不支持 setCustomProperty,可改用系统属性// System.setProperty("mail.smtp.ssl.protocols", "TLSv1.2 TLSv1.1 TLSv1");注意:setCustomProperty在 Hutool 5.7.19+ 中可用。若版本过低,可升级依赖或直接通过System.setProperty临时设置(作用域为整个 JVM)。
✅ 方案二:修改java.security(本文采用的方法)
如果代码级配置无效(比如邮件库内部强制使用了 JVM 全局设置),可直接修改 JDK 的安全策略。
步骤:
找到
java.security文件
该文件位于 JDK 安装目录下的jre/lib/security/java.security。假设你的JAVA_HOME环境变量已正确设置(例如/usr/lib/jvm/java-8-openjdk-amd64),则完整路径为:$JAVA_HOME/jre/lib/security/java.security若
JAVA_HOME未设置,可通过which java和readlink -f获取实际路径,再向上定位到 JDK 根目录。备份原文件(务必执行):
sudocp$JAVA_HOME/jre/lib/security/java.security$JAVA_HOME/jre/lib/security/java.security.bak编辑文件,找到
jdk.tls.disabledAlgorithms=...一行,删除其中的TLSv1和TLSv1.1(保留其他禁用算法):- jdk.tls.disabledAlgorithms=SSLv3, TLSv1, TLSv1.1, RC4, DES, MD5withRSA, ... + jdk.tls.disabledAlgorithms=SSLv3, RC4, DES, MD5withRSA, ...保存退出,重启所有使用该 JDK 的 Java 应用(无需重启操作系统)。
效果:所有基于该 JDK 的 Java 程序均可使用 TLSv1/TLSv1.1,邮件发送恢复正常。
⚠️安全提示:TLSv1 和 TLSv1.1 已被 IETF 标记为废弃,存在已知漏洞。仅在内网或临时应急时采用,后续应推动服务端升级。
✅ 方案三:检查 STARTTLS 配置
端口 587 的标准用法是STARTTLS(先明文连接,再升级为 TLS)。若错误配置为 SSL 直连,会失败。
正确配置:
account.setPort(587);account.setStarttlsEnable(true);// 开启 STARTTLSaccount.setSslEnable(false);// 关闭 SSL 直连若服务商要求必须使用 STARTTLS,且未正确设置,即使协议版本匹配,也可能连接不上。
✅ 方案四:改用 SSL 直连(端口 465)
部分邮件服务商(如阿里云、腾讯企业邮)同时支持465 端口(SMTPS)。此时可直接使用 SSL 加密,无需 STARTTLS。
account.setPort(465);account.setSslEnable(true);// 启用 SSL 直连account.setStarttlsEnable(false);// 关闭 STARTTLS// 同样可指定协议版本(可选)account.setCustomProperty("mail.smtp.ssl.protocols","TLSv1.2 TLSv1.1 TLSv1");注意:需要服务商开放 465 端口且支持 SSL。
⚠️ 方案五:跳过 SSL 证书验证(仅限测试)
此方法用于绕过证书链校验,解决证书过期或自签名问题,但会完全丧失安全性,生产环境绝对禁止。
MailSSLSocketFactorysf=newMailSSLSocketFactory();sf.setTrustAllHosts(true);// 信任所有主机account.setCustomProperty("mail.smtp.ssl.socketFactory",sf);// 或者account.setCustomProperty("mail.smtp.ssl.trust","*");若其他方案无效且只在开发环境,可临时使用以验证是否为证书问题。
✅ 方案六:升级依赖版本
如果 Hutool 或 JavaMail 版本过旧,可能缺少某些配置属性或存在 SSL 相关 Bug。升级至较新版本通常能解决问题。
<!-- pom.xml --><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.28</version><!-- 最新稳定版 --></dependency><dependency><groupId>com.sun.mail</groupId><artifactId>javax.mail</artifactId><version>1.6.2</version><!-- 或 jakarta.mail 3.x --></dependency>升级后,可尝试方案一中的setCustomProperty,通常即可生效。
五、验证与测试
修改后,建议先用以下命令确认服务端支持的 TLS 版本:
openssl s_client-connectsmtp.mxhichina.com:587-starttlssmtp-tls1_2openssl s_client-connectsmtp.mxhichina.com:587-starttlssmtp-tls1_1openssl s_client-connectsmtp.mxhichina.com:587-starttlssmtp-tls1哪个版本能成功握手,就在配置中优先列出该版本。
六、最终选择与效果
我先行尝试了方案一,但不知为何(可能 Hutool 底层未完全覆盖),仍然报错。于是采用方案二(修改java.security),问题立刻解决。后来为了安全,又联系邮件服务商确认是否支持 TLSv1.2,在确认支持后将配置改回仅 TLSv1.2,并移除java.security的修改,最终稳定运行。
个人建议:若你的服务商支持 TLSv1.2,尽量保持 JDK 默认禁用低版本,仅通过代码(方案一)指定协议列表。若服务商确实老旧,再考虑修改全局配置,但务必评估风险。
七、总结
SSLHandshakeException: No appropriate protocol的本质是 TLS 版本协商失败。本文提供的六种方案覆盖了从代码到 JVM、从轻到重的解决路径。希望这篇实战记录能帮助你快速摆脱邮件发送的困扰。
如果你有其他更好的方法,欢迎在评论区补充交流!
发布日期:2026-08-27