最近做了一个发送邮件的功能, 在本地调试完成后,部署到阿里云服务器就一直报错,
Could not connect to SMTP host: smtp.qiye.aliyun.com, port: 465; 网上也搜索了很多的资料,最后花了好几个小时才解决, 报错日志如下
Send mail failed. Error message : Mail server connection failed; nested exception is javax.mail.MessagingException: Could not connect to SMTP host: smtp.qiye.aliyun.com, port: 465;
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 connect to SMTP host: smtp.qiye.aliyun.com, port: 465;
nested exception is:
javax.net.ssl.SSLHandshakeException: No appropriate protocol (protocol is disabled or cipher suites are inappropriate)
总结下来,可能的原因如下,我这里遇到的就属于第四种情况
- 网络或者端口不通
- 邮箱发件服务器地址+端口不正确
- jdk版本不支持
- 发送邮件代码侧问题
排查定位步骤如下
一、 网络或者端口不通
从提示看就是阿里云邮箱的465端口连接不上,一开始以为是阿里云禁止了465端口,所以在阿里云控制台的安全组里面给465端口都配置了入方向和出方向,但仍旧同样报错。 通过shell命令检查网络和端口,都是ok的。(有人说阿里云服务器默认是开了465的,其实无需特意在安全组规则里面配置465)
针对465端口检测,显然也是ok的
#不指定TLS版本
openssl s_client -connect smtp.qiye.aliyun.com:465
#指定TLS版本
openssl s_client -connect smtp.qiye.aliyun.com:465 -tls1_2
二、邮箱发件服务器地址+端口不正确
关于端口方面,早期的邮件端口是25,但目前大多服务商因为垃圾邮件的原因禁止了25端口(所以我这边一开始就用的是465)。阿里云服务器也不例外,但可以在阿里云控制台申请解封25端口,然而是小微服务器一般不给解封的,所以即使本地25端口也调试ok,也不考虑换25端口作为解决方向。
阿里云的stmp地址除了smtp.qiye.aliyun.com外,还有以下两个
smtp.mxhichina.com +465
smtphk.qiye.aliyun.com +80/465
但是经过部署阿里云服务器测试,报错是同样的。于是怀疑是阿里云企业邮箱有啥问题或者阿里云的企业邮箱哪里设置的不对头,所以另外申请了腾讯企业邮箱 smtp.exmail.qq.com+465/587端口进行部署阿里云测试,问题依旧。这波操作下来, 本地ok上阿里云行不通,还是环境的问题需要继续排查。
三、jdk版本不支持
网上有说法,高版本的jdk的http安全限制导致发不去邮件。我这里阿里云服务器jdk版本是1.8.0_372, 本地是 1.8.0_91,都是1.8的jdk,小版本不同应该问题不大,但也进行了尝试。关于jdk主要是两个方面,java.security文件的配置和替换jre目录下的jar包
3.1更改 java.security配置
java.security文件位于jdk安装目录的/jdk1.8.0_372/jre/lib
目录下面,服务器上忘记或者不知道jdk的安装路径的,可以参考如下
which java
ls -lrt /usr/bin/java #其中 /usr/bin/java是第一步的输出结果
ls -lrt /etc/alternatives/java #其中 /etc/alternatives/java是第二步输出的结果
# 如下图,可知安装目录是 /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.372.b07-4.0.2.al8.x86_64
编辑 java.security文件,搜索关键词jdk.tls.disabledAlgorithms=
找到如下位置
注释原来的3行或者删除其中的 SSLv3、TLSv1、TLSv1.1协议(即不禁用这三个加密协议),并保存。效果如下
3.2 用低版本jdk的jar替换高版本jdk的jre目录下的jar包
需要替换的jar文件分别是 local_policy.jar
和US_export_policy.jar
。可以从网上下载低版本的jar或者用本地jdk相同目录下的jar(因为本地能发送成功)去服务器上进行覆盖。这两个jar包的路径位于jdk安装目录下
/jdk1.8.0_372/jre/lib
或者
/jdk1.8.0_372/jre/lib/jre/lib/security/policy/limited
/jdk1.8.0_372/jre/lib/jre/lib/security/policy/unlimited
jdk1.7版本替换包网上下载地址: 点击下载,下载后解压后覆盖到上面的路径下
以上两个步骤操作后,通过阿里云控制台重启了实例和重启应用服务器,报错却依旧。
四、 发送邮件代码侧问题
先看发送邮件核心代码
Properties properties = new Properties();
properties.put("mail.smtp.auth", true);
properties.put("mail.smtp.connectiontimeout", String.valueOf(3 * 1000));
properties.put("mail.smtp.timeout", String.valueOf(3 * 1000));
//启用SSL与指定465端口
properties.put("mail.smtp.ssl.enable", "true");
properties.put("mail.smtp.socketFactory.port", "465");
properties.put("mail.smtp.port", "465");
// SSL来建立连接,失败则回退兼容。如果出于某种原因无法使用SSL来建立连接(比如服务器不支持),则不会回退到非加密的连接方式,而是让连接尝试失败。
properties.put("mail.smtp.ssl.socketFactory", "javax.net.ssl.SSLSocketFactory");
properties.put("mail.smtp.socketFactory.fallback", "false");
这段代码本地ok,上阿里云服务器就连不通stmp+465,故而又从网上找了几个别人写的发送邮件的工具类代码,都是大同小异,结果也是本地ok阿里云服务器不行,着实让人有点蒙圈。后来添加下面这行指定了ssl的加密协议才解决问题
// 握手阶段加密算法不匹配,jre高版本把加密算法禁止了 SSLv3, TLSv1, TLSv1.1等,尝试指定jdk版本或删除相关禁用配置项
props.setProperty("mail.smtp.ssl.protocols", "TLSv1.2");
下面附录一个发送邮件的工具类,以供参考和测试
package com.yulisao.utils.email;
import javax.mail.*;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import java.security.Security;
import java.util.Date;
import java.util.Properties;
public class EmailUtils{
// 此方法调试ok后自行整理成工具类应用到项目中
public static void sendmail() {
try{
//设置SSL连接、邮件环境
Security.addProvider(new com.sun.net.ssl.internal.ssl.Provider());
final String SSL_FACTORY = "javax.net.ssl.SSLSocketFactory";
Properties props = System.getProperties();
//协议
//props.setProperty("mail.transport.protocol", "smtp");
props.setProperty("mail.smtp.host", "smtp.qiye.aliyun.com");//smtp服务器地址
//props.setProperty("mail.smtp.port", "25");//非加密端口
// 使用ssl加密方式,进行如下配置:
props.setProperty("mail.smtp.socketFactory.class", SSL_FACTORY);
props.setProperty("mail.smtp.socketFactory.fallback", "false");
props.setProperty("mail.smtp.socketFactory.port", "465");
// 不指定则是25端口了!注意阿里云禁用此端口
props.setProperty("mail.smtp.port", "465");
props.setProperty("mail.smtp.ssl.enable", "true");
props.setProperty("mail.smtp.ssl.protocols", "TLSv1.2");
props.setProperty("mail.smtp.auth", "true");//表示SMTP发送邮件,需要进行身份验证
props.setProperty("mail.smtp.from", "你的发件邮箱");//mailfrom 参数
props.setProperty("mail.user","你的发件邮箱");//发件人的账号
props.setProperty("mail.password","你的发件邮箱的授权码");// 发件人的账号的密码,如果开启三方客户端安全密码请使用新生产的密码
System.setProperty("mail.mime.splitlongparameters", "false"); //用于解决附件名过长导致的显示异常
//建立邮件会话
Session session = Session.getDefaultInstance(props, new Authenticator() {
//身份认证
protected PasswordAuthentication getPasswordAuthentication() {
//发件人的账号、密码
String userName = props.getProperty("mail.user");
String password = props.getProperty("mail.password");
return new PasswordAuthentication(userName, password);
}
});
//建立邮件对象
MimeMessage message = new MimeMessage(session);
//设置邮件的发件人
InternetAddress from = new InternetAddress("你的发件邮箱","你的邮件主题"); //from 参数,可实现代发,注意:代发容易被收信方拒信或进入垃圾箱。
message.setFrom(from);
//设置邮件的收件人
String[] to = {"abc@163.com","123456789@qq.com"};//收件人列表
InternetAddress[] sendTo = new InternetAddress[to.length];
for (int i=0;i<to.length;i++){
System.out.println("正在执行发送到:" + to[i]);
sendTo[i] = new InternetAddress(to[i]);
}
//传入收件人
message.setRecipients(Message.RecipientType.TO,sendTo);
//设置邮件的主题
message.setSubject("邮件主题");
//设置邮件的文本
String content="邮件内容";
message.setContent(content,"text/html;charset=UTF-8");
//设置时间
message.setSentDate(new Date());
message.saveChanges();
//发送邮件
Transport.send(message);
System.out.println("发送成功!");
}catch(Exception e){
System.out.println("发送邮件异常:"+e.toString());
}
}
// 自测main方法
public static void main(String[] args) {
try{
//设置SSL连接、邮件环境
Security.addProvider(new com.sun.net.ssl.internal.ssl.Provider());
final String SSL_FACTORY = "javax.net.ssl.SSLSocketFactory";
Properties props = System.getProperties();
//协议
//props.setProperty("mail.transport.protocol", "smtp");
props.setProperty("mail.smtp.host", "smtp.qiye.aliyun.com");//smtp服务器地址
//props.setProperty("mail.smtp.port", "25");//非加密端口
// 使用ssl加密方式,进行如下配置:
props.setProperty("mail.smtp.socketFactory.class", SSL_FACTORY);
props.setProperty("mail.smtp.socketFactory.fallback", "false");
props.setProperty("mail.smtp.socketFactory.port", "465");
props.setProperty("mail.smtp.ssl.enable", "true");
props.setProperty("mail.smtp.ssl.protocols", "TLSv1.2");
props.setProperty("mail.smtp.auth", "true");//表示SMTP发送邮件,需要进行身份验证
props.setProperty("mail.smtp.from", "你的发件邮箱");//mailfrom 参数
props.setProperty("mail.user","你的发件邮箱");//发件人的账号
props.setProperty("mail.password","你的发件邮箱的授权码");// 发件人的账号的密码,如果开启三方客户端安全密码请使用新生产的密码
System.setProperty("mail.mime.splitlongparameters", "false"); // 用于解决附件名过长导致的显示异常
//建立邮件会话
Session session = Session.getDefaultInstance(props, new Authenticator() {
//身份认证
protected PasswordAuthentication getPasswordAuthentication() {
//发件人的账号、密码
String userName = props.getProperty("mail.user");
String password = props.getProperty("mail.password");
return new PasswordAuthentication(userName, password);
}
});
//建立邮件对象
MimeMessage message = new MimeMessage(session);
//设置邮件的发件人
InternetAddress from = new InternetAddress("你的发件邮箱","你的邮件主题"); //from 参数,可实现代发,注意:代发容易被收信方拒信或进入垃圾箱。
message.setFrom(from);
//设置邮件的收件人
String[] to = {"abc@163.com","123456789@qq.com"};//收件人列表
InternetAddress[] sendTo = new InternetAddress[to.length];
for (int i=0;i<to.length;i++){
System.out.println("正在执行发送到:" + to[i]);
sendTo[i] = new InternetAddress(to[i]);
}
//传入收件人
message.setRecipients(Message.RecipientType.TO,sendTo);
//设置邮件的主题
message.setSubject("邮件主题");
//设置邮件的文本
String content="邮件内容";
message.setContent(content,"text/html;charset=UTF-8");
//设置时间
message.setSentDate(new Date());
message.saveChanges();
//发送邮件
Transport.send(message);
System.out.println("发送成功!");
}catch(Exception e){
System.out.println("发送邮件异常:"+e.toString());
}
}
}