開始測試前,要確保發郵件的服務器的smtp服務可用。
不然會拋出異常:
Sending the email to the following server failed : m.xxx.com:25
Caused by: javax.mail.AuthenticationFailedException: 334 NTLM supported
然后介紹下我的開發環境(context):目前用的開發框架是playframework,它幫忙封裝了apache的mail工具類,代碼如下:
public static void sendMail(SendMailDto sendMailDto){
if(sendMailDto!=null){
HtmlEmail email = new HtmlEmail();
email.setCharset("UTF-8");// 編碼格式
try {
email.addTo(sendMailDto.accepterEmail);// 接收者
email.setFrom(sendMailDto.sender, sendMailDto.name);// 發送者,姓名
email.setSubject(sendMailDto.title);// 郵件標題
email.setMsg(sendMailDto.content);// 發送內容
Mail.send(email);
Logger.info("接收郵件: "+sendMailDto.accepterEmail+" 發送成功!");
Logger.info("發送郵件服務器: "+sendMailDto.sender);
Logger.info("發送郵件名: "+sendMailDto.name);
}catch (Exception e) {
Logger.info("郵件: "+sendMailDto.accepterEmail+" 發送失敗!");
e.printStackTrace();
}
}
}
在配置文件中要申明:
mail.smtp.host=xxxx
mail.smtp.user=xxxx
mail.smtp.pass=xxxx
本質上還是用的apache的setMailSession()方法。
當一切都配置好了,開始執行,還是報上面的異常,后來發現還需要在配置文件加上 mail.smtp.protocol=smtps,分析下源碼:
public static Session getSession() {
if (session == null) {
Properties props = new Properties();
// Put a bogus value even if we are on dev mode, otherwise JavaMail will complain
props.put("mail.smtp.host", Play.configuration.getProperty("mail.smtp.host", "localhost"));
String channelEncryption;
if (Play.configuration.containsKey("mail.smtp.protocol") && Play.configuration.getProperty("mail.smtp.protocol", "smtp").equals("smtps")) {
// Backward compatibility before stable5
channelEncryption = "starttls";
} else {
channelEncryption = Play.configuration.getProperty("mail.smtp.channel", "clear");
}
if (channelEncryption.equals("clear")) {
props.put("mail.smtp.port", "25");
} else if (channelEncryption.equals("ssl")) {
// port 465 + setup yes ssl socket factory (won't verify that the server certificate is signed with a root ca.)
props.put("mail.smtp.port", "465");
props.put("mail.smtp.socketFactory.port", "465");
props.put("mail.smtp.socketFactory.class", "play.utils.YesSSLSocketFactory");
props.put("mail.smtp.socketFactory.fallback", "false");
} else if (channelEncryption.equals("starttls")) {
// port 25 + enable starttls + ssl socket factory
props.put("mail.smtp.port", "25");
props.put("mail.smtp.starttls.enable", "true");
// can't install our socket factory. will work only with server that has a signed certificate
// story to be continued in javamail 1.4.2 : https://glassfish.dev.java.net/issues/show_bug.cgi?id=5189
}
if (Play.configuration.containsKey("mail.smtp.localhost")) {
props.put("mail.smtp.localhost", Play.configuration.get("mail.smtp.localhost")); //override defaults
}
if (Play.configuration.containsKey("mail.smtp.socketFactory.class")) {
props.put("mail.smtp.socketFactory.class", Play.configuration.get("mail.smtp.socketFactory.class"));
}
if (Play.configuration.containsKey("mail.smtp.port")) {
props.put("mail.smtp.port", Play.configuration.get("mail.smtp.port"));
}
String user = Play.configuration.getProperty("mail.smtp.user");
String password = Play.configuration.getProperty("mail.smtp.pass");
if (password == null) {
// Fallback to old convention
password = Play.configuration.getProperty("mail.smtp.password");
}
String authenticator = Play.configuration.getProperty("mail.smtp.authenticator");
session = null;
if (authenticator != null) {
props.put("mail.smtp.auth", "true");
try {
session = Session.getInstance(props, (Authenticator) Play.classloader.loadClass(authenticator).newInstance());
} catch (Exception e) {
Logger.error(e, "Cannot instanciate custom SMTP authenticator (%s)", authenticator);
}
}
if (session == null) {
if (user != null && password != null) {
props.put("mail.smtp.auth", "true");
session = Session.getInstance(props, new SMTPAuthenticator(user, password));
} else {
props.remove("mail.smtp.auth");
session = Session.getInstance(props);
}
}
if (Boolean.parseBoolean(Play.configuration.getProperty("mail.debug", "false"))) {
session.setDebug(true);
}
}
return session;
}
為了向后兼容,需要在session中設置:
props.put("mail.smtp.port", "25");
props.put("mail.smtp.starttls.enable", "true");
配置好了以后再來嘗試下,發現還是有異常拋出:
javax.mail.MessagingException: Could not convert socket to TLS
sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
不能把socket解析為TLS(通過上面的截圖,可以看到我的郵件服務器是TLS加密了的)
不能對訪問的目標提供有效證書。
到這里有兩種解決方案:
2.把當前smtp host設為可信任 props.put("mail.smtp.ssl.trust", "smtp服務器地址")
到此 所有問題都解決了。
總結下:
產生第一個異常的原因有以下3個:
1. 郵件服務器的smtp沒有開啟。
2. 用戶名密碼錯誤。
3. mail工具類版本較低,不能有效生成socket factory
解決方案:登錄郵箱,在設置里面開啟smtp服務,驗證程序中登錄郵箱的用戶名密碼填寫正確,用高版本的mail.jar 或者在session中設置props.put("mail.smtp.port", "25"); props.put("mail.smtp.starttls.enable", "true");
產生第二異常的原因:
smtp設置了加密,或者嘗試訪問不受信任地址
解決方案:用工具類生成安全證書,放在\jdk1.6.0_31\jre\lib\security下面
關於apache的mail還有很多不明白的地方,上面只是記錄解決問題的過程,和大家共同學習。