javaMail 发送邮件遇到的一些问题

2019-04-14 20:44发布

开发

使用springboot 进行实现邮件发送功能,会简化很多工作。
添加依赖文件
<dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-mailartifactId> dependency>
application.properties 配置文件
下面以163邮箱服务器为例进行配置 # 邮件服务器域名 spring.mail.host=smtp.163.com # 邮件服务器端口号(默认就是25) spring.mail.port=25 # 发送邮件的邮箱 spring.mail.username=666666@163.com # 该邮箱对应的授权码 spring.mail.password=123456 # 超时时间 spring.mail.timeout=20000 ## 编码 spring.mail.encoding=UTF-8 # 是否校验 spring.mail.auth=true # 协议 spring.mail.protocol=smtp # ssl 传输使用ssl协议 # spring.mail.smtp.ssl.enable=false spring-boot-starter-mail 会根据application配置文件中配置的属性,创建 JavaMailSender实例(JavaMailSenderImpl) 建议测试的时候,使用硬编码方式,这样可以随时修改参数。 @Controller public class SenderMailController { @Autowired private JavaMailSender sender;//通过application配置文件的属性创建的JavaMailSender 实例。创建实例的实现类是JavaMailSenderImpl @RequestMapping("/testMail") public String testMail() throws MessagingException, UnsupportedEncodingException { //手动创建的实例的属性和配置文件中的大致相同 JavaMailSenderImpl sender=new JavaMailSenderImpl(); sender.setDefaultEncoding("utf8"); //编码 sender.setHost("smtp.163.com");//163 smtp服务器 sender.setPort(25); //端口 sender.setUsername("666666@163.com"); //邮箱 sender.setPassword("123456");//授权码 sender.setProtocol("smtp"); //协议 //配置额外属性 Properties properties=new Properties();//额外设置的属性 properties.setProperty("mail.smtp.auth", "true");//是否需要验证 properties.setProperty("mail.smtp.timeout","2000");//超时 // properties.setProperty("mail.smtp.ssl.enable", "true");//ssl加密 sender.setJavaMailProperties(properties); //编辑信息 MimeMessage mimeMessage =sender.createMimeMessage();//多媒体信息 MimeMessageHelper helper=new MimeMessageHelper(mimeMessage,true);// 使用MimeMessageHelper类可以简化代码,提高开发效率 helper.setFrom("666666@163.com", "xxx");//设置发送邮件和发送人,该邮箱需要和上面的userName相同 helper.setTo(new String[] {"xxxxx@qq.com"});//目标地址 helper.setSubject("测试!!!");//设置邮件主题 helper.setText("

测试呀

"
,true);//正文 File file =new File("D:/", "test.pdf"); helper.addAttachment(MimeUtility.encodeWord("测试.pdf"), file);//添加附件,并使用MimeUtility解决附件名称中文乱码 sender.send(mimeMessage);//发送邮件 } }
简化版本 @Component public class SendMailUtils { @Autowired private JavaMailSender sender;//有spring容器创建 @Value("${spring.mail.username}") private String username;//使用值注入,避免邮箱发送地址设置错误 /** * * @param to 接受人,邮箱数组字符串 * @param subject 标题 * @param content 文本 * @throws MessagingException * @throws UnsupportedEncodingException */ public void simpleSendMail(String[] to,String subject,String content) throws MessagingException, UnsupportedEncodingException { MimeMessage simpleMessage =sender.createMimeMessage(); MimeMessageHelper helper=new MimeMessageHelper(simpleMessage,true); helper.setFrom(username, "邮箱发送人说明"); helper.setTo(to); helper.setSubject(subject); helper.setText(content,true);//html 数据 sender.send(simpleMessage); } 在这里插入图片描述

遇到的一些问题

之前简单看过邮件发送的程序,觉得很简单真正开始做的时候,还是遇到了很多问题。
Illegal address 。。 Address Reject
  1. 邮箱服务器地址不正确
  2. 邮件服务器拒绝访问,可能服务器对访问ip有限制。
    javax.mail.SendFailedException: Invalid Address Address Reject
java.net.SocketTimeoutException: Read timed out
超时异常。
通过mail.smtp.timeout来设置超时时间,它的单位是毫秒。开始我设置为2000报错了 mail.smtp.timeout =3000

认证相关问题

1. mail.smtp.auth 为true ,报错 javax.mail.AuthenticationFailedException: 535 Error: authentication failed
设置属性 ”mail.smtp.auth“ 为true(需要进行用户名授权码验证),测试邮件发送时报错。 报错的原因可能是
  • 用户名不正确
  • 授权码不正确
创建JavaMailSender对象的时候需要设置密码。该密码是授权码 JavaMailSenderImpl sender=new JavaMailSenderImpl(); sender.setPassword("") //该密码是授权码,而不是邮箱的登陆密码 启用授权码,避免密码泄漏造成邮箱安全隐患,使用授权码是可以访问邮箱的部分功能(发邮件)。使用授权码是无法登陆邮箱的. 开启授权码需要手机验证,对于163邮箱,如下
在这里插入图片描述
2. mail.smtp.auth为false,也会报错 javax.mail.AuthenticationFailedException: 535 Error: authentication failed
其实如果设置 mail.smtp.auth为false,但是如果用户名和密码都不为空,也可能会进行校验的。
因为正是和服务器连接之前,会收集服务器的扩展参数。 如果服务器支持校验而且也设置了用户名和密码,即便mail.smtp.auth为false,也会进行验证。 if ((useAuth || (user != null && password != null)) && (supportsExtension("AUTH") || supportsExtension("AUTH=LOGIN"))) { if (logger.isLoggable(Level.FINE)) logger.fine("protocolConnect login" + ", host=" + host + ", user=" + traceUser(user) + ", password=" + tracePassword(password)); connected = authenticate(user, password); //校验用户名 和 授权码 如果有兴趣,可以自己看一下源码,我也是看的模模糊糊。 下面是SMTPTransport.class 的部分代码 //与服务器建立连接,并进行验证 @Override protected synchronized boolean protocolConnect(String host, int port, String user, String password) throws MessagingException { Properties props = session.getProperties(); // 是否需要校验 boolean useAuth = PropUtil.getBooleanProperty(props, "mail." + name + ".auth", false); //根据传入useAuth user和password的值进行判断 if (useAuth && (user == null || password == null)) { if (logger.isLoggable(Level.FINE)) { logger.fine("need username and password for authentication"); logger.fine("protocolConnect returning false" + ", host=" + host + ", user=" + traceUser(user) + ", password=" + tracePassword(password)); } return false; } //默认使用true boolean useEhlo = PropUtil.getBooleanProperty(props, "mail." + name + ".ehlo", true); if (logger.isLoggable(Level.FINE)) logger.fine("useEhlo " + useEhlo + ", useAuth " + useAuth); //设置默认的host和port if (port == -1) port = PropUtil.getIntProperty(props, "mail." + name + ".port", -1); if (port == -1) port = defaultPort; if (host == null || host.length() == 0) host = "localhost"; boolean connected = false; try { //..........开启服务 boolean succeed = false; if (useEhlo) succeed = ehlo(getLocalHost());//收集服务器的扩展参数 if (!succeed) helo(getLocalHost());// //................ //即便useAuth是false,如果扩展列表的值为true,也会进行验证。这跟邮件服务器的设置有关 if ((useAuth || (user != null && password != null)) && (supportsExtension("AUTH") || supportsExtension("AUTH=LOGIN"))) { if (logger.isLoggable(Level.FINE)) logger.fine("protocolConnect login" + ", host=" + host + ", user=" + traceUser(user) + ", password=" + tracePassword(password)); connected = authenticate(user, password); //校验用户名 和 授权码 return connected; } //..............异常捕获等代码 } supportsExtension 方法,就是从extMap这个HashTable中获取数据,extMap中存放的数据是在ehlo方法中添加的 // private Hashtable extMap; public boolean supportsExtension(String ext) { return extMap != null && extMap.get(ext.toUpperCase(Locale.ENGLISH)) != null; } 与服务器进行连接时,会执行ehlo,获取服务器的服务扩展列表。 protected boolean ehlo(String domain) throws MessagingException { String cmd; if (domain != null) cmd = "EHLO " + domain; else cmd = "EHLO"; sendCommand(cmd); int resp = readServerResponse(); if (resp == 250) { // extract the supported service extensions BufferedReader rd = new BufferedReader(new StringReader(lastServerResponse)); String line; extMap = new Hashtable<>(); try { boolean first = true; while ((line = rd.readLine()) != null) { if (first) { // skip first line which is the greeting first = false; continue; } if (line.length() < 5) continue; // shouldn't happen line = line.substring(4); // skip response code int i = line.indexOf(' '); String arg = ""; if (i > 0) { arg = line.substring(i + 1); line = line.substring(0, i); } if (logger.isLoggable(Level.FINE)) logger.fine("Found extension "" + line + "", arg "" + arg + """); extMap.put(line.toUpperCase(Locale.ENGLISH), arg); //存储获取的扩展列表 } } catch (IOException ex) { } // can't happen } return resp == 250; }
3. com.sun.mail.smtp.SMTPSendFailedException: 553 authentication is required,163 smtp10,DsCowAAXDg8IyftbCG3bBw–.56925S2 1543227657
如果设置mail.smtp.auth为false,并且没有设置密码。
4. javax.mail.AuthenticationFailedException: failed to connect, no password specified?
设置mail.smtp.auth为true,但是没有设置用户名或者密码 下面是 javax.mail.Service中的部分代码 public synchronized void connect(String host, int port, String user, String password) throws MessagingException { //省略其他。。。.。。。。。。。。 if (!connected) { if (authEx != null) throw authEx; else if (user == null) throw new AuthenticationFailedException( "failed to connect, no user name specified?"); else if (password == null) throw new AuthenticationFailedException( "failed to connect, no password specified?"); else throw new AuthenticationFailedException("failed to connect"); } }
5. com.sun.mail.smtp.SMTPSendFailedException: 553 Mail from must equal authorized user
553 Mail from must equal authorized user,邮件的发送者必须和认证的用户一致。 MimeMessage message = mailSender.createMimeMessage(); message .setFrom("666666@163.com"); //必须和设置的userName一致 MimeMessage mimeMessage=new MimeMessage (); mimeMessage.setFrom( new InternetAddress("666666@163.com", "xxx"));

其他问题

1. 添加附件中文名乱码
MimeUtility.encodeWord(fileName)
2. 附件太大也可能会抛出异常
该异常又邮件服务器有关系,一般163,qq等邮件服务器提供的附件大小就能满足大部分情况。
我使用的运维人员搭建的服务器,附件最大只能1M也是笑了。