小江的专栏

夫夷以近,则游者众,险以远,则至者少。

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加密了的)

不能对访问的目标提供有效证书。

到这里有两种解决方案:

1.在网上找个生成安全证书的方法按要求放在指定位置:http://www.oschina.net/question/12_19249

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还有很多不明白的地方,上面只是记录解决问题的过程,和大家共同学习。





阅读更多
个人分类: java
上一篇实时更新viewPager下当前的listview
下一篇教您在项目中快速接入第三方应用
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭