如何强制Java使用TLSv1.2发送邮件?当出现javax.net.ssl.SSLHandshakeException: No appropriate protocol错误时。

7
我正在尝试使用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中添加ssl.protocols属性,值为TLSv1.2,但似乎没有起作用。以下是我的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版本,但它没有解决问题。唯一有效的方法是从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 Mail版本:

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

是 starttls.enable: true 还是 starttl.enable: true,从 enables 中去掉 s? - Sagii
您可以添加 ssl.protocols: TLSv1.2 属性或 ssl.enable: false。 - Sagii
我不确定TLS版本是否是问题所在。考虑“或密码套件不合适”。邮件服务器是否有证书和私钥?它支持哪些密码套件? - user207421
@Sagii,我的属性文件中出现了starttls.enables错误,请感谢您指出它。我已改正,并尝试使用ssl.enable:false代替ssl.protocols:TLSv1.2,但仍然出现相同的错误。 - VishnuVS
@user207421 我正在使用 Gmail 发送邮件。我现在已经检查了支持的密码,并找到了这个链接:https://support.google.com/a/answer/9795993?hl=en - VishnuVS
4个回答

13

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

正如你猜测的那样,你的问题将与JDK中引入的a change相关,以默认禁用不安全的TLS协议版本:

security-libs/javax.net.ssl
禁用TLS 1.0和1.1

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

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

正如bug description中所述,该更改已被反向移植到不同的JDK版本中,您正在使用其中之一。

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

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

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

我真的不明白为什么Spring Boot没有应用这个配置,看起来你定义得很正确。你可以通过查看库的源代码来检查它,当描述邮件属性配置和不同的接受邮件属性时,以及在Spring Boot文档中描述的方式。请尝试像这样定义属性:
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
1
嗨@VishnuVS。不用道歉,非常感谢您授予我赏金。请务必解决问题:如果您修复了测试,请随时与我联系,我会很乐意提供帮助。此外,请注意,更改已经向多个JDK版本进行了回溯,可能也适用于您将使用的Java 11版本。正如我告诉过您的那样,如果您认为我可以提供帮助,请再次与我联系。 - jccampanero
太棒了!这个问题在一个使用最新版本的open-jdk的服务器上困扰我们数月,但在另一个使用稍旧版本的服务器上却没有。只需添加以下内容: props.put("mail.smtp.ssl.protocols", "TLSv1.2"); 就解决了所有问题。 谢谢! - balm
太好了@balm。我很高兴听到答案有帮助,你能解决问题。 - jccampanero

0
You can add this property to your configurations:
            Properties props = javaMailSender.getJavaMailProperties();
            props.put("mail.transport.protocol", "smtp");
            props.put("mail.smtp.auth", "true");
            props.put("mail.smtp.starttls.enable", "true");
            props.put("mail.debug", "true");
            props.put("mail.smtp.ssl.trust", "*");
            props.put("mail.smtp.ssl.protocols", "TLSv1.2");
        

1
你的回答可以通过添加更多支持性信息来改进。请编辑以添加进一步的细节,比如引用或文档,以便其他人能够确认你的回答是否正确。你可以在帮助中心找到关于如何编写良好回答的更多信息。 - Diego Borba

-1

您可以通过设置 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 设置为支持的协议除了断言现有默认值之外还能做什么。如果有什么作用,它只会使情况变得更糟,因为它会添加所有支持但被禁用的协议,例如匿名协议。 - user207421

-2

当我使用application.properties时,遇到了类似的问题,错误信息如下:

-dev.miku.r2dbc.mysql.client.MySqlConnectionClosedException: 连接意外关闭 -d.m.r.mysql.client.ReactorNettyClient : 错误:没有适当的协议(协议已禁用或密码套件不合适) -12-16 14:48:30.287 警告 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。 - matt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接