1. 程式人生 > >java Mail發郵件 smtp被TLS加密認證不了的解決方案

java Mail發郵件 smtp被TLS加密認證不了的解決方案

開始測試前,要確保發郵件的伺服器的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還有很多不明白的地方,上面只是記錄解決問題的過程,和大家共同學習。