문제
서버에서 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 로 설정 되었는데요
Gmail의 smtp 프로토콜은 TLS 연결이 필요하다고 합니다.
ssl 속성을 boolean ssl = false;
로 변경해주면 일단 에러 메시지가 바뀝니다.
TLS 1.2
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
을 검색 해 본 결과는 아래와 같습니다.
이 중 JavaMail API JAR
의 javax.mail-api
는 컴파일에만 적합 합니다. 실질적으로 코드를 실행하기 위해서는 완전한 구현체인 JavaMail API
의 javax.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
구현체는 완전 무료고 오픈 소스기 때문에 맘편히 프로덕트에 포함시킬 수 있다고 합니다.
자세한 JavaMail의 라이센스에 대한 내용은 아래 링크를 참고 해 주세요.
이상입니다.
References
'Development > Daily Error' 카테고리의 다른 글
Cannot find a (Map) Key deserializer for type 해결 (0) | 2022.11.17 |
---|---|
[iRods] 데이터 오브젝트가 포함된 리소스 삭제하기 CAT_RESOURCE_NOT_EMPTY (0) | 2022.10.17 |
[POI] 엑셀의 숫자를 소수점으로 파싱하는 문제 해결하기 (0) | 2022.08.25 |
AJAX POST 요청시 Status Code: 302 하며 /denied로 리다이렉트 시키는 문제 해결 (0) | 2022.08.21 |
[Spring Redis] incompatible types for field 해결 (0) | 2022.08.17 |