Bug01_服务器Docker环境下,邮件发送超时 & javax.net.ssl.SSLHandshakeException: No appropriate protocol
一、Bug1:邮件发送超时
Bug1描述:
我的Java项目在用户注册激活时有邮件发送需求,开发环境下localhost能够通过25号端口实现邮件发送;部署项目到生产环境下centos系统的服务器,出现邮件发送连接超时现象。
Bug1处理:
起初我以为如下日志信息是报错信息:
DEBUG SMTP: useEhlo true, useAuth true
DEBUG SMTP: trying to connect to host "smtp.163.com", port 465, isSSL false
后来发现开发环境下成功实现邮件发送同样会有此日志信息。
经调研,是阿里云服务器将25号端口封闭,无法通过此端口向外发送数据,需向阿里云提交申请解封。另一种解决方式是将服务器发送数据的端口改为465。为了修改端口,以邮件发送为目的,我更新后的代码如下:
[1] 相关的配置文件信息如下:
spring.mail.protocol=smtp
spring.mail.host=smtp.163.com
spring.mail.port=465
spring.mail.username=salieri97@163.com
spring.mail.password=MSWLTEBIGNKFXQVW
spring.mail.default-encoding=utf-8
spring.mail.properties.mail.debug=true
spring.mail.sendAddressId=http://120.79.133.235:8989/salieri/user/activation
[2] 以注册账户激活为目的,邮件服务模块如下:
package com.salieri.service.serviceImpl;
import com.salieri.service.MailService;
import com.salieri.utils.TrustAllTrustManager;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.net.ssl.*;
import java.io.UnsupportedEncodingException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.Properties;
// 网易邮箱使用465端口发送邮件,默认的25端口在阿里云服务器被封
@Service
@Transactional
public class MailServiceImpl implements MailService {
// 发送人的邮箱账号
@Value("${spring.mail.username}")
private String myEmailAccount;
// 客户端授权密码
@Value("${spring.mail.password}")
private String myEmailPassword;
// 网易163邮箱的SMTP服务器地址
@Value("${spring.mail.host}")
private String myEmailSMTPHost;
private static final String title = "激活邮件";
// 激活账号邮件并发送
@Override
public void sendMailForActivationAccount(String activationUrl, String name) throws KeyManagementException, NoSuchAlgorithmException { // activationUrl是邮件发送内容,name是接收方邮箱名
// 直接通过主机认证
HostnameVerifier hv = new HostnameVerifier() {
public boolean verify(String urlHostName, SSLSession session) {
return true;
}
};
// 配置认证管理器
javax.net.ssl.TrustManager[] trustAllCerts = {new TrustAllTrustManager()};
SSLContext sc = SSLContext.getInstance("SSL");
SSLSessionContext sslsc = sc.getServerSessionContext();
sslsc.setSessionTimeout(0);
sc.init(null, trustAllCerts, null);
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
String emailMsg = "点击链接:"+activationUrl+" 进行账户激活--EasyBuy";
// 1. 创建参数配置, 用于连接邮件服务器的参数配置
// 参数配置
Properties props = new Properties();
// 使用的协议(JavaMail规范要求)
props.setProperty("mail.transport.protocol", "smtp");
// 发件人的邮箱的 SMTP 服务器地址
props.setProperty("mail.smtp.host", myEmailSMTPHost);
// 需要请求认证
props.setProperty("mail.smtp.auth", "true");
// SSLSocketFactory类的端口
props.put("mail.smtp.socketFactory.port", "465");
// SSLSocketFactory类
props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
props.put("mail.smtp.auth", "true");
// 网易提供的ssl加密端口,QQ邮箱也是该端口
props.put("mail.smtp.port", "465");
// 2. 根据配置创建会话对象, 用于和邮件服务器交互
Session session = Session.getDefaultInstance(props);
// 设置为debug模式, 可以查看详细的发送 log
session.setDebug(true);
// 1. 创建一封邮件
MimeMessage message = new MimeMessage(session);
try {
// 2. From: 发件人
message.setFrom(new InternetAddress(myEmailAccount, title, "UTF-8"));
// 3. To: 收件人(可以增加多个收件人、抄送、密送)
message.setRecipient(MimeMessage.RecipientType.TO, new InternetAddress(name, "个人网站", "UTF-8"));
// 4. Subject: 邮件主题
message.setSubject(title, "UTF-8");
// 5. Content: 邮件正文(可以使用html标签)
message.setContent(emailMsg, "text/html;charset=UTF-8");
// 6. 设置发件时间
message.setSentDate(new Date());
// 7. 保存设置
message.saveChanges();
// 4. 根据Session获取邮件传输对象
Transport transport = session.getTransport();
// 5. 使用 邮箱账号 和 密码 连接邮件服务器, 这里认证的邮箱必须与 message 中的发件人邮箱一致, 否则报错
transport.connect(myEmailAccount, myEmailPassword);
// 6. 发送邮件, 发到所有的收件地址, message.getAllRecipients() 获取到的是在创建邮件对象时添加的所有收件人, 抄送人, 密送人
transport.sendMessage(message, message.getAllRecipients());
// 7. 关闭连接
transport.close();
} catch (MessagingException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
[3] TrustAllTrustManager工具类代码如下:
package com.salieri.utils;
public class TrustAllTrustManager implements javax.net.ssl.TrustManager, javax.net.ssl.X509TrustManager {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
public boolean isServerTrusted(java.security.cert.X509Certificate[] certs) {
return true;
}
public boolean isClientTrusted(java.security.cert.X509Certificate[] certs) {
return true;
}
public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType)
throws java.security.cert.CertificateException {
return;
}
public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType)
throws java.security.cert.CertificateException {
return;
}
}
二、Bug2: javax.net.ssl.SSLHandshakeException: No appropriate protocol
Bug2描述:
在解决Bug1后,客户端前端Vue-cli程序调用服务器端Java后端邮件接口时再次出现邮件发送不成功现象(在本地测试是通过的),查看服务器端SringBoot输出日志发现javax.net.ssl.SSLHandshakeException: No appropriate protocol报错。
Bug2处理:
通过调研,发现是证书问题,于是依据解决方案1修改服务器端openjdk1.8配置。由于我在服务器端通过Docker部署,在容器运行时修改了jdk配置文件,命令行执行过程如下:
docker ps
docker inspect 588cb39e4b9b # 588cb39e4b9b是jdk1.8的容器编号,此命令是为了查看jdk1.8内部配置文件位置
docker exec -it 588cb39e4b9b bash # 进入jdk1.8容器
cd /usr/local/openjdk-8/lib/security
apt-get update
apt-get install vim
vim java.security
修改的地方参考解决方案1,而后重启springboot的jar包+jdk.18组合成的容器,在本地前端调用服务器后端接口发送邮件成功。