Development/Daily Error

[Java Mail] Could not convert socket to TLS; 문제 해결

📝 작성 : 2022.10.12  ⏱ 수정 : 
반응형

문제

서버에서 javax email을 활용해 구글 이메일을 전송 할 때 아래와 같은 에러가 발생 했습니다.

org.springframework.mail.MailSendException: Mail server connection failed; nested exception is 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). 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)
; message exception details (1) are:
Failed message 1:
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)
    at com.sun.mail.smtp.SMTPTransport.startTLS(SMTPTransport.java:1907)
    at com.sun.mail.smtp.SMTPTransport.protocolConnect(SMTPTransport.java:666)
    at javax.mail.Service.connect(Service.java:295)
    ... 

원인

물론 이메일 전송이 안되는 이유는 다양합니다. 윈도우를 사용하고 있다면 백신이 막고 있는 경우가 굉장히 흔하다고 하는데 일단 리눅스 서버에서 이메일 전송을 하고 있기 때문에 백신의 문제는 배제했습니다.

SSLHandshakeException 에 집중해서 원인을 찾아 보았고, 저의 경우는 두가지 원인이 있었는데 하나는 SSL 설정, 그리고 두번째는 TLS 버전 이었습니다.

이메일 전송에 대한 테스트는 아래와 같이 작성 했습니다.

SendEmailTest.java

package kr.re.kisti.idr.email;

import org.junit.jupiter.api.Test;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.mail.javamail.MimeMessageHelper;

import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import java.io.UnsupportedEncodingException;
import java.util.Properties;

class SendEmailTest {

    final String HOST = "smtp.gmail.com";
    final int PORT = 587;
    final String FROM = "이메일 보낼 구글 메일주소";
    final String PASSWORD = "Gmail APP 비밀번호 https://myaccount.google.com/security  에서 설정";
    String emailTo = "이메일 받을 주소";

    boolean auth = true;
    boolean starttls = true;
    boolean sslTrust = true;
    boolean ssl = false;

    @Test
    public void sendMAil() throws UnsupportedEncodingException, javax.mail.MessagingException {

        JavaMailSender sender = javaMailSender();
        MimeMessage message = sender.createMimeMessage();
        MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
        InternetAddress to = new InternetAddress();
        to.setAddress(emailTo);
        to.setPersonal(emailTo, "UTF-8");

        helper.setFrom(FROM);
        helper.setTo(to);
        helper.setSubject("email title");
        helper.setText("email text");

        sender.send(message);
    }

    public JavaMailSender javaMailSender() {
        JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
        mailSender.setHost(HOST);
        mailSender.setPort(PORT);
        mailSender.setUsername(FROM);
        mailSender.setPassword(PASSWORD);

        Properties props = mailSender.getJavaMailProperties();
        props.setProperty("mail.transport.protocol", "smtp");

        props.setProperty("mail.smtp.auth", String.valueOf(auth));
        if (starttls)
            props.setProperty("mail.smtp.starttls.enable", "true");
        if (sslTrust)
            props.setProperty("mail.smtp.ssl.trust", "*");
        if (ssl)
            props.setProperty("mail.smtp.ssl.enable", "true");
        return mailSender;
    }

}

첫번째에는 위의 ssl이 true로 되어서 mail.smtp.ssl.enable 이 true 로 설정 되었는데요

image-20221012165500601

https://developers.google.com/gmail/imap/imap-smtp

Gmail의 smtp 프로토콜은 TLS 연결이 필요하다고 합니다.

ssl 속성을 boolean ssl = false;로 변경해주면 일단 에러 메시지가 바뀝니다.

TLS 1.2

image-20221012170213859

https://security.googleblog.com/2018/10/modernizing-transport-security.html

2018년 10월 구글은 Google Security Blog 에 Chrome 72 버전부터 TLS1.0과 1.1의 지원을 끊겠다고 공지 했는데요. 공지는 따로 찾아 볼 수 없었지만 마찬가지로 구글 메일에서도 낮은 버전의 TLS 프토토콜 지원을 막아 버린 걸로 예상됩니다.

해결

두가지 해결 방법이 있었습니다.

1. 프로토콜 강제 설정

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

위의 코드에서 ssl 프로토콜을 TLSv1.2로 강제 해 주면 문제 없이 이메일이 전송 됩니다.

2. java mail 버전 변경

mvnrepository 에서 javax mail 을 검색 해 본 결과는 아래와 같습니다.

image-20221012165650603

이 중 JavaMail API JARjavax.mail-api 는 컴파일에만 적합 합니다. 실질적으로 코드를 실행하기 위해서는 완전한 구현체인 JavaMail APIjavax.mail을 사용해야 하며 javax.mail.api를 사용한다면 java.lang.NoClassDefFoundError 에러가 발생 합니다.

변경 전

pom.xml

<dependency>
    <groupId>com.sun.mail</groupId>
    <artifactId>javax.mail</artifactId>
    <version>1.4.7</version>
</dependency>

변경 후

pom.xml

<dependency>
    <groupId>com.sun.mail</groupId>
    <artifactId>javax.mail</artifactId>
    <version>1.6.2</version>
</dependency>

javax.mail 의 버전을 변경하면 낮은 버전의 TLS로 요청하다가 생기는 문제가 사전에 방지됩니다.

버전 변경 후에는 mail.smtp.ssl.protocols 설정 없이도 이메일 전송이 성공합니다.

라이센스

오라클의 javax.mail을 사용하기 때문에 라이센스가 찝찝해 관련해서도 찾아 보았는데 다행히도 JavaMail API 구현체는 완전 무료고 오픈 소스기 때문에 맘편히 프로덕트에 포함시킬 수 있다고 합니다.

image-20221012171041237

https://javaee.github.io/javamail/FAQ#free

자세한 JavaMail의 라이센스에 대한 내용은 아래 링크를 참고 해 주세요.

image-20221012171126604

https://javaee.github.io/javamail/JavaMail-License

이상입니다.

References

반응형