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

  • Post author:
  • Post category:java




开发

使用springboot 进行实现邮件发送功能,会简化很多工作。



添加依赖文件
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-mail</artifactId>
		</dependency>


application.properties 配置文件

下面以163邮箱服务器为例进行配置


################# mail 基本 属性配置 #####################
## 超时时间
spring.mail.timeout=10000
## 编码
spring.mail.default-encoding=utf-8
spring.mail.encoding=utf-8
## 校验
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
spring.mail.properties.mail.smtp.starttls.required=true
spring.mail.protocol=smtp
spring.mail.properties.mail.debug=true
spring.mail.properties.mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFactory

# ssl  传输使用ssl协议
# 如果使用了 对应的SSL 协议端口,那么需要 设置为 true 
# spring.mail.smtp.ssl.enable=false
#  或者使用 下面的其中一种方式
# spring.mail.properties.mail.smtp.ssl.enable=true
# spring.mail.properties.mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFactory
# spring.mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFactory

############  自定义的邮件发送设置 ##############
# 邮件服务器域名
spring.mail.host=smtp.163.com  
# 邮件服务器端口号(默认就是25)
spring.mail.port=25
# 发送邮件的邮箱
spring.mail.username=666666@163.com
# 该邮箱对应的授权码
spring.mail.password=123456

如果需要使用SSL 协议 可以设置以下端口号

在这里插入图片描述

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("<p>测试呀</p>",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


org.springframework.mail.MailSendException: Mail server connection failed;
org.springframework.mail.MailSendException: Mail server connection failed; 
 nested exception is javax.mail.MessagingException:
  Could not connect to SMTP host: smtp.exmail.qq.com, port: 465, response: -1. 
  Failed messages: javax.mail.MessagingException: Could not connect to SMTP host: smtp.exmail.qq.com, port: 465, response: -1; 

原因是 使用的端口号是 SSL 协议端口,必须通过ssl验证。

# spring.mail.properties.mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFactory

spring:
  mail:
    properties:
      mail:
        smtp:
          auth: true
          socketFactory:
            class: javax.net.ssl.SSLSocketFactory

或者

# spring.mail.properties.mail.smtp.ssl.enable=true
spring:
  mail:
    properties:
      mail:
        smtp:
          ssl:
            enable: true



认证相关问题



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<String, String> 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也是笑了。



版权声明:本文为yamadeee原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。