javax.net.ssl.SSLHandshakeException:没有合适的协议。如何强制 Java 使用 TLSv1.2 发送邮件?

Posted

技术标签:

【中文标题】javax.net.ssl.SSLHandshakeException:没有合适的协议。如何强制 Java 使用 TLSv1.2 发送邮件?【英文标题】:javax.net.ssl.SSLHandshakeException:No appropriate protocol. How to force Java to use TLSv1.2 for sending mail? 【发布时间】:2021-10-21 17:36:25 【问题描述】:

我正在尝试使用 JavaMailSender 在 Spring Boot 中发送邮件,但出现此错误:

javax.net.ssl.SSLHandshakeException: No appropriate protocol (protocol is disabled or cipher suites are inappropriate). Failed messages: javax.mail.MessagingException: Could not convert socket to TLS;
  nested exception is:
    javax.net.ssl.SSLHandshakeException: No appropriate protocol (protocol is disabled or cipher suites are inappropriate)

阅读后发现是因为我们使用的 TLSv1 或 TLSv1.1 已经过时了,我们应该使用 v1.2 或更高版本。我尝试在 application.yml 中添加值为 TLSv1.2 的 ssl.protocols 属性,但它似乎不起作用。这是我的 application.yml:

spring:
  mail:
    host: smtp.gmail.com
    port: 587
    username: ******@gmail.com
    password: *******
    protocol: smtp
    tls: true
    properties.mail.smtp:
      auth: true
      ssl.trust: smtp.gmail.com
      starttls.required: true
      starttls.enabled: true
      ssl.protocols: TLSv1.2

这是发送邮件的方法:

public void sendEmail(String to, String subject, String body) 
    logger.info("Sending mail to : " + to);
    SimpleMailMessage mail = new SimpleMailMessage();
    mail.setTo(to);
    mail.setSubject(subject);
    mail.setText(body);

    try 
        javaMailSender.send(mail);
        logger.info("Mail sent to " + to);
     catch (Exception e) 
        logger.error("Could not send mail to " + to + ". Exception : " + e.getMessage());
    

我更新了 JavaMailSender 版本,但没有解决问题。唯一可行的方法是按照this answer 中的建议从 jdk.tls.disabledAlgorithms 中删除 TLSv1 和 TLSv1.1,但这只是一个临时修复。如何让邮件发件人使用 TLSv1.2 或更高版本?我在 application.yml 中定义 ssl.protocols 属性的方式有什么问题吗?

这是我使用的java版本:

openjdk version "1.8.0_292"
OpenJDK Runtime Environment (AdoptOpenJDK)(build 1.8.0_292-b10)
OpenJDK 64-Bit Server VM (AdoptOpenJDK)(build 25.292-b10, mixed mode)

以及spring boot starter邮件版:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
    <version>2.5.3</version>
</dependency>

【问题讨论】:

是 starttls.enables: true 还是 starttls.enable: true 从 enable 中去掉 s 您可以添加 ssl.protocols : TLSv1.2 属性或 ssl.enable : false 我不相信 TLS 版本是问题所在。考虑“或密码套件不合适”。邮件服务器是否有证书和私钥?以及它支持哪些密码套件。 @Sagii 它是我的属性文件中的 starttls.enables,感谢您指出。我更正了它,并尝试提供 ssl.enable:false 而不是 ssl.protocols:TLSv1.2,但仍然是同样的错误。 @user207421 我正在使用 gmail 发送邮件。我现在检查了支持的密码,发现了这个support.google.com/a/answer/9795993?hl=en 【参考方案1】:

我最近回答了一个几乎相同的question。

如您所料,您的问题将与 JDK 中引入的 a change 有关,以便默认禁用不安全的 TLS 协议版本:

安全库/javax.net.ssl ➜ 禁用 TLS 1.0 和 1.1

TLS 1.0 和 1.1 是 TLS 协议的版本,它们不再是 被认为是安全的,并已被更安全和现代的取代 版本(TLS 1.2 和 1.3)。

这些版本现已默认禁用。如果遇到 问题,您可以自担风险,通过删除重新启用版本 jdk.tls.disabledAlgorithms 中的“TLSv1”和/或“TLSv1.1” java.security 配置文件中的安全属性。

正如您在bug description 中看到的那样,更改已向后移植到不同的 JDK 版本,即您正在使用的版本。

为了解决这个问题,您可以编辑您的java.security 配置文件并从jdk.tls.disabledAlgorithms 安全属性中删除TLSv1 和/或TLSv1.1

此外,您可以在配置属性中明确包含所需的 TLS 协议:

props.put("mail.smtp.ssl.protocols", "TLSv1.2");

老实说,我不明白为什么 Spring Boot 不应用此配置,看来您定义正确。您可以在描述mail properties configuration 和不同的accepted mail properties 时查看库的源代码,以及Spring Boot documentation 中的描述。请尝试像这样定义属性:

spring:
  mail:
    properties:
      # other properties you need
      "mail.smtp.ssl.protocols": "TLSv1.2"

为了排除一些可能与 YAML 格式有关的问题,您可以尝试在 JavaMaiSenderImpl 中显式设置此属性,仅用于测试:

public void sendEmail(String to, String subject, String body) 
    logger.info("Sending mail to : " + to);
    SimpleMailMessage mail = new SimpleMailMessage();
    mail.setTo(to);
    mail.setSubject(subject);
    mail.setText(body);

    try 
        // Just for validating the solution
        JavaMailSenderImpl javaMailSenderImpl = (JavaMailSenderImpl)javaMailSender;
        // Please, put a breakpoint here and debug the configured
        // properties, it will provide you a valuable information
        // about the actual properties that have been applied      
javaMailSenderImpl.getJavaMailProperties().put("mail.smtp.ssl.protocols", "TLSv1.2");

        javaMailSender.send(mail);
        logger.info("Mail sent to " + to);
     catch (Exception e) 
        logger.error("Could not send mail to " + to + ". Exception : " + e.getMessage());
    

【讨论】:

很抱歉这么晚才回复。我们正在更新我们的 java 版本,不知何故我们的集成测试都搞砸了,所以我无法验证这一点。我想在我们修复测试后回复,但我认为它不会很快修复。幸运的是,我在 Java 11 中没有遇到这个错误。无论如何我都会奖励你@jccampanero 嗨@VishnuVS。请,没有必要道歉。非常感谢你给我赏金。无论如何,我希望您能真正解决问题:当您修复测试时,请随时与我联系,如果可以,我将很乐意提供帮助。此外,请注意,该更改已向后移植到多个 JDK 版本,可能也移植到您将用于 Java 11 的那个版本。正如我告诉你的,如果你认为我可以提供帮助,请再次与我联系。 太棒了!这个问题几个月来一直困扰着我们在一台使用最新版本的 open-jdk 的服务器上,但在另一台使用非常旧版本的服务器上却没有。只需添加: props.put("mail.smtp.ssl.protocols", "TLSv1.2");到处都修好了。谢谢! 这很棒@balm。我很高兴听到这个答案很有帮助并且您能够解决您的问题。【参考方案2】:

我在使用 application.properties 时遇到了类似的问题 我得到的错误是:

-dev.miku.r2dbc.mysql.client.MySqlConnectionClosedException:连接意外关闭 -d.m.r.mysql.client.ReactorNettyClient:错误:没有合适的协议(协议被禁用或密码套件不合适) -12-16 14:48:30.287 WARN 27232 --- [actor-tcp-nio-2] d.m.r.mysql.client.ReactorNettyClient:连接已被对等方关闭 2021-12-16 14:48:30.288 错误 27232 --- [actor-tcp-nio-2] reactor.core.publisher.Operators :操作员称为默认 onErrorDropped

这是我的解决方案:

一行代码就解决了这个问题:

spring.r2dbc.properties.ssl= false

【讨论】:

您在哪里添加“一行”来解决问题?另外,如果我没记错的话,这似乎正在关闭 ssl。【参考方案3】:

您可以通过设置System.setProperty("mail.smtp.ssl.protocols", protocols); 来实现。例如:

String protocols = String.join(" ", 
    SSLContext
        .getDefault()
        .getSupportedSSLParameters()
        .getProtocols()
);

System.setProperty("mail.smtp.ssl.protocols", protocols);

无论如何,此配置无需任何配置即可工作

  mail:
    host: smtp.gmail.com
    port: 587
    username: ***@gmail.com
    password: P4ssw0rd
    properties:
      mail:
        smtp:
          starttls:
            enable: true
            required: true
      auth: true

【讨论】:

很难看出将mail.smtp.ssl.protocols 设置为支持的协议除了断言现有的默认值外,还能做什么。如果有的话,通过添加所有受支持但禁用的协议(例如匿名协议)会使情况变得更糟。

以上是关于javax.net.ssl.SSLHandshakeException:没有合适的协议。如何强制 Java 使用 TLSv1.2 发送邮件?的主要内容,如果未能解决你的问题,请参考以下文章