《Java Mail》
《Java Mail》
文/冯皓林
完稿:2016.3.16--2016.3.19
“特定环境、一类问题、N个解决方案”
一、RFC821文档说明
核心:
邮件(Mail):
1.邮件头(Mail-Header):邮件头包含与传输、投递邮件有关的基本信息。
2.邮件体(Mail-Body):(1)邮件正文;(2)邮件附件
注:邮件头与邮件体之间以空行进行分隔,邮件头中不允许出现空行。
RFC821文档规定了如何编写一封简单的邮件(纯文本邮件),一封简单的邮件包含邮件头和邮件体两个部分,邮件头和邮件体之间使用空行分隔。
邮件头包含的内容有:
1. from字段 --用于指明发件人
2. to字段 --用于指明收件人
3. subject字段 --用于说明邮件主题
4. cc字段 -- 抄送,将邮件发送给收件人的同时抄送给另一个收件人,收件人可以看到邮件抄送给了谁
5. bcc字段 -- 密送,将邮件发送给收件人的同时将邮件秘密发送给另一个收件人,收件人无法看到邮件密送给了谁
邮件体指的就是邮件的具体内容。
RFC821邮件协议实例:
1. Return-Path: <it315_test@sina.com> // 邮件的回复地址 2. Delivered-To: it315_test@mx72.mail.sohu.com 3. Received: from smtp.sina.com.cn (unknown [202.108.3.177]) 4. by sohumx139.sohu.com (Postfix) with SMTP id E4F9802C1249 5. for <it315_test@sohu.com>; Thu, 10 Nov 2005 16:39:50 +0800 (CST) // 该字段的基本格式为Received from A by B for C, 其中A为发送方,B为接收方,C为收件人的邮箱地址。 该字段的内容由邮件的SMTP服务器填写,常常用来追踪邮件传输路线和分析邮件的来源。
6. Received: (qmail 49221 invoked from network); 10 Nov 2005 08:39: 33 -0000 7. Received: from unknown (HELO it315?test) (218.246.5.151) 8. by smtp.sina.com.cn with SMTP; 10 Nov 2005 08:39:33 -0000 9. From: it315_test@sina.com // 用于指定发件人的地址 10.To: it315_test@sohu.com // 用于指定收件人的地址 11.Subject: test // 该字段用于指定邮件的主题 12.Message-Id: <20051110083950.E4F9802C1249@sohumx139.sohu.com> 13.Date: Thu, 10 Nov 2005 16:39:50 +0800 (CST) // 用于指定邮件发送的时间 14.Status: RO 15.X-UIDL: 1131611863.21509_77.mx72 //1-15 为 邮件头 16. // 邮件头和邮件体之间以一个空行 17.test!!! //邮件体 |
注:RFC821的协议:SMPT
Smtp命令格式:
smtp, simple mail transfer protocol. (邮件发送协议)
SMTP命令及格式 |
说 明 |
Ehlo<SP><domain><CRLF> |
ehlo命令是SMTP邮件发送程序与SMTP邮件接收程序建立连接后必须发送的第一条SMTP命令,参数<domain>表示SMTP邮件发送者的主机名。ehlo命令用于替代传统SMTP协议中的helo命令。 |
Auth<SP>Login<CRLF> |
如果SMTP邮件接收程序需要SMTP邮件发送程序进行认证时,它会向SMTP邮件发送程序提示它所采用的认证方式,SMTP邮件发送程序接着应该使用这个命令回应SMTP邮件接收程序,参数<para>表示回应的认证方式,通常是SMTP邮件接收程序先前提示的认证方式。 |
Mail<SP>From:<reverse-path><CRLF> |
此命令用于指定邮件发送者的邮箱地址,参数<reverse-path>表示发件人的邮箱地址。 |
Rcpt<SP>To:<forword-path><CRLF> |
此命令用于指定邮件接收者的邮箱地址,参数<forward-path>表示接收者的邮箱地址。如果邮件要发送给多个接收者,那么应使用多条Rcpt<SP>To命令来分别指定每一个接收者的邮箱地址。 |
Data<CRLF> |
此命令用于表示SMTP邮件发送程序准备开始传送邮件内容,在这个命令后面发送的所有数据都将被当作邮件内容,直至遇到“<CRLF>.<CRLF>”标识符,则表示邮件内容结束。 |
Quit<CRLF> |
此命令表示要结束邮件发送过程,SMTP邮件接收程序接收到此命令后,将关闭与SMTP邮件发送程序的网络连接。 |
注:<SP> 加一个空格,space
<CRLF> carriage-return-line-feed 回车换行
ehlo : 即extend helo,对helo的扩展
下面是使用客户端工具,连接smtp服务器(发邮件服务器),经过登录验证后,遵循RFC822文档规范撰写邮件正文的原理图:
SMTP工作原理:
1) 客户端使用TCP协议连接SMTP服务器的25端口;
2) 客户端发送HELO报文将自己的域地址告诉给SMTP服务器;
3) SMTP服务器接受连接请求,向客户端发送请求账号密码的报文(AUTH LOGIN);
4) 客户端向SMTP服务器传送账号和密码,如果验证成功,向客户端发送一个OK命令,表示可以开始报文传输;
5) 客户端使用MAIL命令将邮件发送者的名称发送给SMTP服务器;
6) SMTP服务器发送OK命令做出响应;
7) 客户端使用RCPT命令发送邮件接收者地址,如果SMTP服务器能识别这个地址,就向客户端发送OK命令,否则拒绝这个请求;
8) 收到SMTP服务器的OK命令后,客户端使用DATA命令发送邮件的数据。
9) 客户端发送QUIT命令终止连接。
Smtp状态图:
下面用Telnet工具(或X-shell4客户端工具)来演示:
更多的细节请参考RFC821和RFC822文档:
http://www.chinaitlab.com/linux/manual/develop/rfc/RFC821.txt
https://datatracker.ietf.org/doc/rfc822/?include_text=1
POP3协议(邮件接收协议):
POP3: post office protocol-version3 (邮局协议版本3)
POP3(Post Office Protocol 3,邮局协议版本3)主要用于支持使用客户端远程管理在服务器上的电子邮件。该协议是在RFC-1939中定义的,是Internet上的大多数人用来接收邮件的机制。POP3采用Client/Server工作模式,默认使用TCP 110端口。
* 在使用POP协议时,人们熟悉的很多功能,如查看收到了多少新邮件消息的功能,POP根本不支持。这些功能都内置到诸如Eudora或 Microsoft Outlook之类的邮件程序中,能为您记住接收的上一封邮件,以及计算有多少新邮件这类信息。因此,如果想获取这类信息,将需要由自己进行计算。
POP3协议命令:
命令及其使用格式 |
说 明 |
user<SP>username<CssRLF> |
user命令是POP3客户端程序与POP3邮件服务器建立连接后通常发送的第一条命令,参数username表示收件人的帐户名称。 |
pass<SP>password<CRLF> |
pass命令是在user命令成功通过后,POP3客户端程序接着发送的命令,它用于传递帐户的密码,参数password表示帐户的密码。 |
stat<CRLF> |
stat命令用于查询邮箱中的统计信息,例如,邮箱中有邮件数和邮件占用的字节大小等。 |
list<SP>[msg#]<CRLF> |
list命令用于列出邮箱中的邮件信息,参数msg#是一个可选参数,表示邮件的序号。当不指定参数时,POP3服务器列出邮箱中所有的邮件信息;当指定参数msg#时,POP3服务器只返回该序号对应的邮件的信息。 |
retr<SP>msg#<CRLF> |
retr命令用于获取某封邮件的内容,参数msg#表示邮件的序号。 |
dele<SP>msg#<CRLF> |
dele命令用于在某封邮件上设置删除标记,参数msg#表示邮件的序号。POP3服务器执行dele命令时,只是为邮件设置了删除标记,并没有真正把邮件删除掉,只有POP3客户端程序发出quit命令后,POP3服务器才会真正删除所有设置了删除标记的邮件。 |
rset<CRLF> |
rset命令用于清除所有邮件的删除标记。 |
noop<CRLF> |
noop命令用于检测POP3客户端与POP3服务器的连接情况。 |
quit<CRLF> |
quit命令表示要结束邮件接收过程,POP3服务器接收到此命令后,将删除所有设置了删除标记的邮件,并关闭与POP3客户端程序的网络连接。 |
下面是使用客户端工具,连接POP3服务器(发邮件服务器),经过登录验证后,接收邮件正文的原理图:
POP3工作原理:
1) 客户端使用TCP协议连接邮件服务器的110端口;
2) 客户端使用USER命令将邮箱的账号传给POP3服务器;
3) 客户端使用PASS命令将邮箱的账号传给POP3服务器;
4) 完成用户认证后,客户端使用STAT命令请求服务器返回邮箱的统计资料;
5) 客户端使用LIST命令列出服务器里邮件数量;
6) 客户端使用RETR命令接收邮件,接收一封后便使用DELE命令将邮件服务器中的邮件置为删除状态;
7) 客户端发送QUIT命令,邮件服务器将将置为删除标志的邮件删除,连接结束。
(注:客户端UA可以设定将邮件在邮件服务器上保留备份,而不将其删除。)
POP3状态图:
下面用Telnet工具(或X-shell4客户端工具)来演示:
二、MIME协议介绍
核心:
MIME邮件也是由邮件头和邮件体两大部分组成。对照RFC822, MIME在Internet E-mail报文中增加了五个新头域,即MIME-Version, Content-Type, Content-Transfer-Encoding,
Content-ID和Content-Description。
IME, 全称为“Multipurpose Internet Mail Extensions”, 比较确切的中文名称为“多用途互联网邮件扩展”。它是当前广泛应用的一种电子邮件技术规范,基本内容定义于RFC 2045-2049(注意RFC1521和RFC1522是它的过时版本)。
RFC822 文档定义了邮件内容的主体结构和各种邮件头字段的详细细节,但是,它没有定义邮件体的格式,RFC822文档定义的邮件体部分通常都只能用于表述一段普通的文本,而无法表达出图片、声音等二进制数据。另外,SMTP服务器在接收邮件内容时,当接收到只有一个“.”字符的单独行时,就会认为邮件内容已经结束,如果一封邮件正文中正好有内容仅为一个“.”字符的单独行,SMTP服务器就会丢弃掉该行后面的内容,从而导致信息丢失。 由于 Internet的迅猛发展,人们已不满足于电子邮件仅仅是用来交换文本信息,而希望使用电子邮件来交换更为丰富多彩的多媒体信息,例如,在邮件中嵌入图片、声音、动画和附件。但是,由于图片和声音等内容是非ASCII码的二进制数据,而RFC822邮件格式只适合用来表达纯文本的邮件内容,所以,要使用 RFC822邮件格式发送这些非ASCII码的二进制数据时,必须先采用某种编码方式将它们“编码”成可打印的ASCII字符后再作为RFC822邮件格式的内容。
MIME试图在不改变SMTP协议和RFC822(邮件格式标准)的基础上,使得邮件可以传送任意二进制文件。它并没有改动或取代RFC822格式,但增加了邮件的主体结构,为此,它在这些协议之上,采取了一些措施:
邮件阅读程序在读取到这种经过编码处理的邮件后,再按照相应的解码方式解码出原始的二进制数据,这样就可以借助RFC822邮件格式来传递多媒体数据了。
两个问题:
(1) 邮件阅读程序如何知道邮件中嵌入的原始二进制数据所采用的编码方式;
(2) 邮件阅读程序如何知道每个嵌入的图像或其他资源在整个邮件内容中的起止位置。
在我们的实际开发当中,一封邮件既可能包含图片,又可能包含有附件,在这样的情况下,RFC822文档规定的邮件格式就无法满足要求了。
可见,MIME邮件与普通的RFC822邮件的关系犹如Java编程语言中的子类与父类的关系,子类是对父类的扩展,子类功能更强大,但子类离不开父类的支持。如果需要了解MIME的详细细节,可以查阅RFC 2045~2049系列文档。
MIME协议是对RFC822文档的升级和补充,它描述了如何生产一封复杂的邮件。通常我们把MIME协议描述的邮件称之为MIME邮件。MIME协议描述的数据称之为MIME消息。
对于一封复杂邮件,如果包含了多个不同的数据,MIME协议规定了要使用分隔线对多段数据进行分隔,并使用Content-Type头字段对数据的类型、以及多个数据之间的关系进行描述。
1 From: "bhw98" <bhw98@sina.com> // 发件人的地址 2 Reply-To: bhw98@sina.com // 回复地址 3 To: <bluesky7810@163.com> // 收件人地址 4 Subject: Re: help // 主题 5 X-Mailer: Foxmail 4.2 [cn] // 自定义的、非标准的域名都以X-开头,例如:X-Mailer,X-MSMail-Priority等,通常在接收和发送邮件的是同一程序才能理解它们的意义。 6 Mime-Version: 1.0 // MIME版本 7 Content-Type: multipart/alternative; // 邮件体的内容类型 “主类型/子类型” 内容类型域 主类型有text, image, audio, video, application, multipart, message等,分别表示文本、图片、音频、视频、应用、分段、消息。 text/html, text/plain, text/xml, text/css等。 *以X-开头的主类型和子类型,同样表示自定义类型,未向IANA正式注册,但大多已经约定成俗了。 例如:application/x-zip-compressed是ZIP文件类型。 各种各种类型一般都可以带参数。至于参数的形式,RFC里有很多补充规定,有的允许带几个参数。 特别说明: multipart类型:multipart/mixed, multipart/related, multipart/alternative multipart类型层次关系图:
[注释:]1-7行为邮件头,8-82行邮件体 8 boundary="=====002_Dragon307572345230_=====" // 段头:声明“boundary”参数字符串 9 10 11 This is a multi-part message in MIME format. 12
13 --=====002_Dragon307572345230_===== // 字段之间定界:“--”+boundary 14 Content-Type: text/plain; charset="GB2312" // 纯文本类型,带了charset参数 15 Content-Transfer-Encoding: quoted-printable 16 //域即传送编码域,它用来说明后面传输的内容的编码方式。 Content-Transfer-Encoding共有Base64, Quoted-printable, 7bit, 8bit, Binary等几种。 默认编码方式:7bit 电子邮件源码最初设计为全部是可打印的ASCII码的形式。 非ASCII码的文本或数据要编码成要求的格式: Base64, Quoted-Printable是在非英语国家使用最广的编码方式。 Binary方式只具有象征意义,而没有任何实用价值。
17 bluesky7810=A3=AC=C4=FA=BA=C3=A3=A1 18 19 =A1=A1=A1=A1=D4=DA=CF=C2=C6=AA=D7=EE=BA=F3=BF=C9=D2=D4=CF=C2=D4=D8=B0=A1=A3=AC=C4=E3 ... ... ... ... 30 =A1=A1=A1=A1=A1=A1=A1=A1=A1=A1=A1=A1=A1=A1=A1=A1=A1=A1=A1=A1=A1=A1=A1=A1=A12003-04-07 31 32 --=====002_Dragon307572345230_===== 33 Content-Type: text/html; charset="GB2312" 34 Content-Transfer-Encoding: quoted-printable 35 36 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> 37 <HTML><HEAD> 38 <META content=3D"text/html; charset=3Dgb2312"= 39 http-equiv=3DContent-Type> 40 <META content=3D"MSHTML 5.00.2920.0" name=3DGENERATOR> ... ... ... ... 79 </HTML> 80 81 --=====002_Dragon307572345230_=====-- // 分段结束符号 ”--”+boundary+”--” 82 |
三、使用JavaMail创建邮件和发送邮件
JavaMail创建的邮件是基于MIME协议的。因此可以使用JavaMail创建出包含图片,包含附件的复杂邮件。
3.1、JavaMail API的简单介绍
3.1、使用JavaMail API发送邮件
3.1.1普通的纯文本的邮件
Java代码 //演示javamail发送邮件 public class MailDemo1 {
public static void main(String[] args) throws Exception { /** * 1)创建一次会话(邮件会话) Session 2)创建一封邮件,撰写邮件 MimeMessage 3)发送邮件 Transport
*/
//1)创建会话 /** * 参数一:属性参数 mail.host 服务器地址 mail.smtp.auth: 表示使用验证方式登录(base64) * 参数二: 进行Base64编码后的数据 */ Properties prop = new Properties(); prop.setProperty("mail.host", "smtp.126.com"); prop.setProperty("mail.smtp.auth", "true"); Session session = Session.getDefaultInstance(prop, new Authenticator() { @Override protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication("你的邮箱的地址","你的密码"); }
}); //打开dubug session.setDebug(true);
//2)创建一封邮件 MimeMessage mail = new MimeMessage(session);
//修改邮件
//发件人 mail.setFrom(new InternetAddress("发件人的邮箱地址")); //收件人 //参数一:收件类型 TO: 表示发送 CC: 抄送 BCC: 密送 // A -> B(TO) // A -> B(TO) C(CC) // A -> B(TO) C(CC) D(BC)
mail.setRecipient(RecipientType.TO, new InternetAddress("收件人的邮箱地址"));
//主题 mail.setSubject("这是邮件的主题-第一封javamail邮件");
//正文 mail.setContent("这是第一封邮件的正文", "text/plain;charset=utf-8");
//3)发送邮件 Transport.send(mail);
}
} |
控制台输出的debug信息: |
|
3.1.2 带有html内容的邮件
Java代码: //演示javamail发送带有html内容的邮件 public class MailDemo2 {
public static void main(String[] args) throws Exception { /** * 1)创建一次会话(邮件会话) Session 2)创建一封邮件,撰写邮件 MimeMessage 3)发送邮件 Transport
*/
//1)创建会话 /** * 参数一:属性参数 mail.host 服务器地址 mail.smtp.auth: 表示使用验证方式登录(base64) * 参数二: 进行Base64编码后的数据 */ Properties prop = new Properties(); prop.setProperty("mail.host", "smtp.163.com"); prop.setProperty("mail.smtp.auth", "true"); Session session = Session.getDefaultInstance(prop, new Authenticator() { @Override protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication("你的邮箱地址","你的邮箱密码"); }
}); //打开dubug session.setDebug(true);
//2)创建一封邮件 MimeMessage mail = new MimeMessage(session);
//修改邮件
//发件人 mail.setFrom(new InternetAddress("发件人的邮箱地址")); //收件人 //参数一:收件类型 TO: 表示发送 CC: 抄送 BCC: 密送 // A -> B(TO) // A -> B(TO) C(CC) // A -> B(TO) C(CC) D(BC)
mail.setRecipient(RecipientType.TO, new InternetAddress("收件人的邮箱地址"));
//主题 mail.setSubject("这是Dante的测试邮件-第2封javamail邮件"); //正文 mail.setContent("<a href='http://www.cnblogs.com/dantefung/'>Dante Fung的博客</a><br/><font color='red' size='8'>编程 数学 设计 英文</font>", "text/html;charset=utf-8"); //3)发送邮件 Transport.send(mail);
}
} |
控制台输出: DEBUG: setDebug: JavaMail version 1.4.5 DEBUG: getProvider() returning javax.mail.Provider[TRANSPORT,smtp,com.sun.mail.smtp.SMTPTransport,Sun Microsystems, Inc] DEBUG SMTP: useEhlo true, useAuth true DEBUG SMTP: useEhlo true, useAuth true DEBUG SMTP: trying to connect to host "smtp.163.com", port 25, isSSL false 220 163.com Anti-spam GT for Coremail System (163com[20141201]) DEBUG SMTP: connected to host "smtp.163.com", port: 25
EHLO YOS-01505151223 250-mail 250-PIPELINING 250-AUTH LOGIN PLAIN 250-AUTH=LOGIN PLAIN 250-coremail 1Uxr2xKj7kG0xkI17xGrU7I0s8FY2U3Uj8Cz28x1UUUUU7Ic2I0Y2UrEgCpMUCa0xDrUUUUj 250-STARTTLS 250 8BITMIME DEBUG SMTP: Found extension "PIPELINING", arg "" DEBUG SMTP: Found extension "AUTH", arg "LOGIN PLAIN" DEBUG SMTP: Found extension "AUTH=LOGIN", arg "PLAIN" DEBUG SMTP: Found extension "coremail", arg "1Uxr2xKj7kG0xkI17xGrU7I0s8FY2U3Uj8Cz28x1UUUUU7Ic2I0Y2UrEgCpMUCa0xDrUUUUj" DEBUG SMTP: Found extension "STARTTLS", arg "" DEBUG SMTP: Found extension "8BITMIME", arg "" DEBUG SMTP: Attempt to authenticate DEBUG SMTP: check mechanisms: LOGIN PLAIN DIGEST-MD5 NTLM DEBUG SMTP: AUTH LOGIN command trace suppressed DEBUG SMTP: AUTH LOGIN succeeded DEBUG SMTP: use8bit false MAIL FROM:<6637213hao@163.com> 250 Mail OK RCPT TO:<6637213hao@163.com> 250 Mail OK DEBUG SMTP: Verified Addresses DEBUG SMTP: 6637213hao@163.com DATA 354 End data with <CR><LF>.<CR><LF> From: 6637213hao@163.com To: 6637213hao@163.com Message-ID: <11648642.0.1458375886925.JavaMail.Administrator@smtp.163.com> Subject: =?UTF-8?Q?=E8=BF=99=E6=98=AFDant?= =?UTF-8?Q?e=E7=9A=84=E6=B5=8B=E8=AF=95=E9=82=AE=E4=BB=B6-?= =?UTF-8?Q?=E7=AC=AC2=E5=B0=81javamail=E9=82=AE=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/html;charset=utf-8 Content-Transfer-Encoding: quoted-printable
<a href=3D'http://www.cnblogs.com/dantefung/'>Dante Fung=E7=9A=84=E5=8D=9A= =E5=AE=A2</a><br/><font color=3D'red' size=3D'8'>=E7=BC=96=E7=A8=8B =E6=95= =B0=E5=AD=A6 =E8=AE=BE=E8=AE=A1 =E8=8B=B1=E6=96=87</font> . 250 Mail OK queued as smtp7,C8CowACnVm+QDe1WqEW5Aw--.5413S2 1458376082 QUIT 221 Bye
|
163邮箱网页客户端页面:
|
3.1.3带附件的邮件
Java代码: //演示javamail发送带有附件的邮件 public class MailDemo3 {
public static void main(String[] args) throws Exception { /** * 1)创建一次会话(邮件会话) Session 2)创建一封邮件,撰写邮件 MimeMessage 3)发送邮件 Transport
*/
//1)创建会话 /** * 参数一:属性参数 mail.host 服务器地址 mail.smtp.auth: 表示使用验证方式登录(base64) * 参数二: 进行Base64编码后的数据 */ Properties prop = new Properties(); prop.setProperty("mail.host", "smtp.163.com"); prop.setProperty("mail.smtp.auth", "true"); Session session = Session.getDefaultInstance(prop, new Authenticator() { @Override protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication("你的邮箱地址","你的邮箱密码"); }
});
//打开dubug session.setDebug(true);
//2)创建一封邮件 MimeMessage mail = new MimeMessage(session);
//修改邮件
//发件人 mail.setFrom(new InternetAddress("发件人的邮箱地址")); //收件人 //参数一:收件类型 TO: 表示发送 CC: 抄送 BCC: 密送 // A -> B(TO) // A -> B(TO) C(CC) // A -> B(TO) C(CC) D(BC)
mail.setRecipient(RecipientType.TO, new InternetAddress("收件人的邮箱地址")); //主题 mail.setSubject("这是邮件的主题-第5封javamail邮件");
//创建附件 File file = new File("C:/mm.jpg"); MimeBodyPart bodyPart = new MimeBodyPart(); bodyPart.setDataHandler(new DataHandler(new FileDataSource(file))); bodyPart.setFileName(file.getName());//附件名称
//创建附件总类 MimeMultipart mmp = new MimeMultipart(); mmp.addBodyPart(bodyPart);
//把所有附件放到邮件中 mail.setContent(mmp);
//3)发送邮件 Transport.send(mail);
}
}
|
控制台输出: DEBUG: setDebug: JavaMail version 1.4.5 DEBUG: getProvider() returning javax.mail.Provider[TRANSPORT,smtp,com.sun.mail.smtp.SMTPTransport,Sun Microsystems, Inc] DEBUG SMTP: useEhlo true, useAuth true DEBUG SMTP: useEhlo true, useAuth true DEBUG SMTP: trying to connect to host "smtp.163.com", port 25, isSSL false 220 163.com Anti-spam GT for Coremail System (163com[20141201]) DEBUG SMTP: connected to host "smtp.163.com", port: 25
EHLO YOS-01505151223 250-mail 250-PIPELINING 250-AUTH LOGIN PLAIN 250-AUTH=LOGIN PLAIN 250-coremail 1Uxr2xKj7kG0xkI17xGrU7I0s8FY2U3Uj8Cz28x1UUUUU7Ic2I0Y2UFlzWcuUCa0xDrUUUUj 250-STARTTLS 250 8BITMIME DEBUG SMTP: Found extension "PIPELINING", arg "" DEBUG SMTP: Found extension "AUTH", arg "LOGIN PLAIN" DEBUG SMTP: Found extension "AUTH=LOGIN", arg "PLAIN" DEBUG SMTP: Found extension "coremail", arg "1Uxr2xKj7kG0xkI17xGrU7I0s8FY2U3Uj8Cz28x1UUUUU7Ic2I0Y2UFlzWcuUCa0xDrUUUUj" DEBUG SMTP: Found extension "STARTTLS", arg "" DEBUG SMTP: Found extension "8BITMIME", arg "" DEBUG SMTP: Attempt to authenticate DEBUG SMTP: check mechanisms: LOGIN PLAIN DIGEST-MD5 NTLM DEBUG SMTP: AUTH LOGIN command trace suppressed DEBUG SMTP: AUTH LOGIN succeeded DEBUG SMTP: use8bit false MAIL FROM:<6637213hao@163.com> 250 Mail OK RCPT TO:<6637213hao@163.com> 250 Mail OK DEBUG SMTP: Verified Addresses DEBUG SMTP: 6637213hao@163.com DATA 354 End data with <CR><LF>.<CR><LF> From: 6637213hao@163.com To: 6637213hao@163.com Message-ID: <25443492.1.1458291368362.JavaMail.Administrator@smtp.163.com> Subject: =?UTF-8?B?6L+Z5piv6YKu5Lu255qE5Li76aKYLeesrDXlsIFqYXZhbWFpbOmCruS7tg==?= MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="----=_Part_0_14365489.1458291368288"
------=_Part_0_14365489.1458291368288 Content-Type: image/jpeg; name=mm.jpg Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename=mm.jpg
------=_Part_0_14365489.1458291368288-- . 250 Mail OK queued as smtp14,EsCowEDp8Ulow+tWLFJAAA--.16153S2 1458291562 QUIT 221 Bye |
网页:
|
3.1.4 QQ邮箱的授权码限制
详情请查看QQ邮箱帮助中心:
http://service.mail.qq.com/cgi-bin/help?subtype=1&&id=28&&no=1001256
Java代码: public class MailTool { public static void main(String[] args) throws MessagingException, GeneralSecurityException { Properties props = new Properties();
// 开启debug调试 props.setProperty("mail.debug", "true"); // 发送服务器需要身份验证 props.setProperty("mail.smtp.auth", "true"); // 设置邮件服务器主机名 props.setProperty("mail.host", "smtp.qq.com"); // 发送邮件协议名称 props.setProperty("mail.transport.protocol", "smtp");
MailSSLSocketFactory sf = new MailSSLSocketFactory(); sf.setTrustAllHosts(true); props.put("mail.smtp.ssl.enable", "true"); props.put("mail.smtp.ssl.socketFactory", sf);
Session session = Session.getInstance(props);
Message msg = new MimeMessage(session); msg.setSubject("这是Dante的测试邮件"); StringBuilder builder = new StringBuilder(); builder.append("url = " + "http://www.cnblogs.com/dantefung/"); builder.append("\nDante的博客"); builder.append("\n当前的时间为: " + new Date().getTime()); msg.setText(builder.toString()); msg.setFrom(new InternetAddress("这里写上发件人的邮箱地址(你的邮箱地址)"));
Transport transport = session.getTransport(); transport.connect("smtp.qq.com", "这里写上你自己的邮箱地址", "QQ的授权码");
transport.sendMessage(msg, new Address[] { new InternetAddress("这里写上收件人的邮箱地址") }); transport.close(); } } |
控制台输出: DEBUG: JavaMail version 1.4.5 DEBUG: successfully loaded resource: /META-INF/javamail.default.providers DEBUG: Tables of loaded providers DEBUG: Providers Listed By Class Name: {com.sun.mail.smtp.SMTPSSLTransport=javax.mail.Provider[TRANSPORT,smtps,com.sun.mail.smtp.SMTPSSLTransport,Sun Microsystems, Inc], com.sun.mail.smtp.SMTPTransport=javax.mail.Provider[TRANSPORT,smtp,com.sun.mail.smtp.SMTPTransport,Sun Microsystems, Inc], com.sun.mail.imap.IMAPSSLStore=javax.mail.Provider[STORE,imaps,com.sun.mail.imap.IMAPSSLStore,Sun Microsystems, Inc], com.sun.mail.pop3.POP3SSLStore=javax.mail.Provider[STORE,pop3s,com.sun.mail.pop3.POP3SSLStore,Sun Microsystems, Inc], com.sun.mail.imap.IMAPStore=javax.mail.Provider[STORE,imap,com.sun.mail.imap.IMAPStore,Sun Microsystems, Inc], com.sun.mail.pop3.POP3Store=javax.mail.Provider[STORE,pop3,com.sun.mail.pop3.POP3Store,Sun Microsystems, Inc]} DEBUG: Providers Listed By Protocol: {imaps=javax.mail.Provider[STORE,imaps,com.sun.mail.imap.IMAPSSLStore,Sun Microsystems, Inc], imap=javax.mail.Provider[STORE,imap,com.sun.mail.imap.IMAPStore,Sun Microsystems, Inc], smtps=javax.mail.Provider[TRANSPORT,smtps,com.sun.mail.smtp.SMTPSSLTransport,Sun Microsystems, Inc], pop3=javax.mail.Provider[STORE,pop3,com.sun.mail.pop3.POP3Store,Sun Microsystems, Inc], pop3s=javax.mail.Provider[STORE,pop3s,com.sun.mail.pop3.POP3SSLStore,Sun Microsystems, Inc], smtp=javax.mail.Provider[TRANSPORT,smtp,com.sun.mail.smtp.SMTPTransport,Sun Microsystems, Inc]} DEBUG: successfully loaded resource: /META-INF/javamail.default.address.map DEBUG: getProvider() returning javax.mail.Provider[TRANSPORT,smtp,com.sun.mail.smtp.SMTPTransport,Sun Microsystems, Inc] DEBUG SMTP: useEhlo true, useAuth true DEBUG SMTP: trying to connect to host "smtp.qq.com", port 465, isSSL true 220 smtp.qq.com Esmtp QQ Mail Server DEBUG SMTP: connected to host "smtp.qq.com", port: 465
EHLO YOS-01505151223 250-smtp.qq.com 250-PIPELINING 250-SIZE 73400320 250-AUTH LOGIN PLAIN 250-AUTH=LOGIN 250-MAILCOMPRESS 250 8BITMIME DEBUG SMTP: Found extension "PIPELINING", arg "" DEBUG SMTP: Found extension "SIZE", arg "73400320" DEBUG SMTP: Found extension "AUTH", arg "LOGIN PLAIN" DEBUG SMTP: Found extension "AUTH=LOGIN", arg "" DEBUG SMTP: Found extension "MAILCOMPRESS", arg "" DEBUG SMTP: Found extension "8BITMIME", arg "" DEBUG SMTP: Attempt to authenticate DEBUG SMTP: check mechanisms: LOGIN PLAIN DIGEST-MD5 NTLM DEBUG SMTP: AUTH LOGIN command trace suppressed DEBUG SMTP: AUTH LOGIN succeeded DEBUG SMTP: use8bit false MAIL FROM:<476400902@qq.com> 250 Ok RCPT TO:<476400902@qq.com> 250 Ok DEBUG SMTP: Verified Addresses DEBUG SMTP: 476400902@qq.com DATA 354 End data with <CR><LF>.<CR><LF> From: 476400902@qq.com Message-ID: <18405969.0.1458377114421.JavaMail.Administrator@smtp.qq.com> Subject: =?UTF-8?B?6L+Z5pivRGFudGXnmoTmtYvor5Xpgq7ku7Y=?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: quoted-printable
url =3D http://www.cnblogs.com/dantefung/ Dante=E7=9A=84=E5=8D=9A=E5=AE=A2 =E5=BD=93=E5=89=8D=E7=9A=84=E6=97=B6=E9=97=B4=E4=B8=BA=EF=BC=9A 14583771133= 42 . 250 Ok: queued as QUIT 221 Bye
|
|
四、关于Java web中的邮箱激活
目的:防止用户恶意注册无效的邮箱,避免数据库中存入脏数据。
原理:用户从前台注册信息,控制器(Servlet/Action)中的注册方法接收前台传过来注册信息(用户名、密码、邮箱等),生成token(令牌),即激活码,然后拼接在URL后边(例如:http://localhost:8080/TestDemo/emailcheck?id=xxx&token=xxxx,这个链接是链回你自己的网站),发送一封邮件到用户的邮箱,当用户点击了该链接后,直接访问你网站的emailcheck对应的servlet中的激活方法,此时,你只需验证用户带回来的token与你之前生成的token是否一致。一致,则激活成功。不一致,则激活失败。
思路:
1.数据库加两个字,state字段(0:未激活,1:激活成功),token:(放激活码)
2.用户填写资料,插入数据成功,state字段默认是0,同时生成一个token也存入数据库
3.提示用户激活,发送邮件,邮件中带一个激活成功页的URL,URL里有两个参数(1,用户ID,2:激活码)
4.用户点击链接,回到激活成功页,激活成功页的Load事件,得到两个参数,以这两个参数为条件查询数据库里的数据,如果有,修改字段state为1,反之,提示激活失败,重新激活。
时序图:
项目目录规划:
注册页面: <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>用户注册页面</title>
<meta http-equiv="pragma" content="no-cache"> <meta http-equiv="cache-control" content="no-cache"> <meta http-equiv="expires" content="0"> <!-- <link rel="stylesheet" type="text/css" href="styles.css"> -->
</head>
<body> <form action="${pageContext.request.contextPath }/userServlet?op=register" method="post"> 用户名:<input type="text" name="username"/><br> 密码:<input type="password" name="password"/><br> 邮箱:<input type="text" name="email"/><br> <input type="submit" value="提交"/> </form> </body> </html> |
UserServlet: import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.UUID;
import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession;
import com.dantefung.entity.User; import com.dantefung.mail.SendMail; import com.dantefung.util.MD5Util; /** * ClassName:UserServlet <br/> * Function: TODO ADD FUNCTION. <br/> * Reason: TODO ADD REASON. <br/> * Date: 2016-3-26 上午11:05:12 <br/> * @author Dante Fung * @version * @since JDK 1.6 * @see */ public class UserServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String op = request.getParameter("op"); if("register".equals(op)) { register(request, response); } else if("emailCheck".equals(op)) { mailCheck(request, response); } }
private void mailCheck(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1、根据ID查询用户记录中的激活码 // 2、比较激活码是否一致 HttpSession session = request.getSession(); String token = request.getParameter("token"); String tem = (String)session.getAttribute("TOKEN"); System.out.println(tem + "==" + token); if(tem.equals(token)) { request.setAttribute("msg", "邮箱激活成功!请登录。"); } else { request.setAttribute("msg", "邮箱激活失败!"); } request.getRequestDispatcher("/mail/message.jsp").forward(request, response); }
private void register(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 总体思路: //1.数据库加两个字,state字段(0:未激活,1:激活成功),token:(放激活码) //2.用户填写资料,插入数据成功,state字段默认是0,同时生成一个token也存入数据库 //3.提示用户激活。。。发送邮件。。。邮件中带一个激活成功页的URL,URL里有两个参数(1,用户ID,2:激活码) //4.用户点击链接,回到激活成功页。。。激活成功页的Load事件,得到两个参数,以这两个参数为条件查询数据库里的数据,如果有,修改字段state为1,反之。。提示激活失败,重新激活。。
// 获取用户注册信息 String pwd = MD5Util.md5(request.getParameter("password")); String username = request.getParameter("username"); String email = request.getParameter("email"); User user = new User(); user.setEmail(email); user.setUsername(username); user.setPassword(pwd);
// 生成激活码的规则:用户邮箱 + 用户密码 + 当前时间 =>进行MD5加密=>激活码 Calendar c = Calendar.getInstance(); long time = c.getTimeInMillis(); String token = MD5Util.md5(email+pwd+time); // 激活码过期时间 //String token_exptime = (time + 1000*60*60*24) + ""; String token_exptime = (time + 1000 + 20) + "";
String id=UUID.randomUUID().toString().replace("-", ""); // TODO 将用户信息存入数据库:insert into tb_user(id,username,pwd,token,token_exptime,regtime,status) values (?,?,?,?,?,sysdate,0) // 这里用session暂存token,模拟token存入数据库 HttpSession session = request.getSession(); session.setAttribute("TOKEN", token);
// 准备邮件内容 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); StringBuffer sb = new StringBuffer("<div style=\"width:660px;overflow:hidden;border-bottom:1px solid #bdbdbe;\"><div style=\"height:52px;overflow:hidden;border:1px solid #464c51;background:#353b3f url(http://www.lofter.com/rsc/img/email/hdbg.png);\"><a href=\"http://www.lofter.com?mail=qbclickbynoticemail_20120626_01\" target=\"_blank\" style=\"display:block;width:144px;height:34px;margin:10px 0 0 20px;overflow:hidden;text-indent:-2000px;background:url(http://i.imgur.com/uBv21zA.png) no-repeat;\">Dante's Blog</a></div>"+"<div style=\"padding:24px 20px;\">您好,"+email+"<br/><br/>Dante是一个\"专注兴趣、分享创作\"的人,旨在为\"热爱记录生活、追求时尚品质、崇尚自由空间\"的你,打造一个全新而定展示平台!<br/><br/>请点击下面链接激活账号,24小时生效,否则重新注册账号,链接只能使用一次,请尽快激活!</br>"); sb.append("<a href=\"http://localhost:8080/TestDemo/userServlet?op=emailCheck&id="); sb.append(id); sb.append("&token="); sb.append(token); sb.append("\">http://localhost:8080/TestDemo/userServlet?op=emailCheck&id="); sb.append(id); sb.append("&token="); sb.append(token); sb.append("</a>"+"<br/>如果以上链接无法点击,请把上面网页地址复制到浏览器地址栏中打开<br/><br/><br/>Dante,专注兴趣,分享创作<br/>"+sdf.format(new Date())+ "</div></div>" );
//发送邮件 /* SendEmail.send(email, sb.toString());*/ SendMail sendMail = new SendMail(user); sendMail.setInfo(sb.toString()); sendMail.start(); request.setAttribute("msg", "恭喜您,注册成功,请到您的注册邮箱验证!"); request.getRequestDispatcher("/mail/message.jsp").forward(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); }
} |
MD5Util: import java.security.MessageDigest;
//MD5工具类 public class MD5Util {
/** * 密码转换的方法 * @param password 原始密码 * @return md5加密后的密码 */ public static String md5(String password){ try { MessageDigest md = MessageDigest.getInstance("md5"); //转换 byte[] result = md.digest(password.getBytes()); StringBuffer sb = new StringBuffer(); for(byte b: result){ // //System.out.print(b+" "); sb.append(byteToHexString(b)); } return sb.toString(); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); } } // -127 -36 -101 -37 82 -48 77 -62 0 54 -37 -40 49 62 -48 85 16位(10进制) // 81dc9bdb52d04dc20036dbd8313e d0 55 32位(16进制)
/** * 把一个指定的10进制字节数字,转为2位的16进制的字符串 * @param b * @return */ public static String byteToHexString(byte b){
int target = b; //如果是负数则+256,再计算16进制的数字 if(target<0){ target=target+256; }
int first = target/16; int second = target%16;
return hexString[first]+hexString[second]; }
private static String[] hexString = {"0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"};
public static void main(String[] args) { System.out.println(md5("1234")); // 81dc9bdb52d04dc20036dbd8313ed055 81dc9bdb52d04dc20036dbd8313ed055 } } |
SendMail: import javax.mail.Message; import javax.mail.Session; import javax.mail.Transport; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage;
import com.dantefung.entity.User; /** * ClassName:SendMail <br/> * Function: TODO ADD FUNCTION. <br/> * Reason: TODO ADD REASON. <br/> * Date: 2016-3-21 下午3:18:21 <br/> * @author Dante Fung * @version * @since JDK 1.6 * @see */ public class SendMail extends Thread { // 发件人地址 private String from = "发件人的邮箱"; // 用户名 private String username = "您的邮箱"; // 密码 private String password = "您的邮箱密码"; // 邮件发送的服务器地址:smtp.qq.com smtp.126.com smtp.163.com等 private String host = "smtp.163.com";
private String info = null;
private User user;
public SendMail(User user) { this.user = user; }
@Override public void run() { try { // 1、准备一些基本的参数配置 Properties prop = new Properties(); prop.setProperty("mail.host", host); prop.setProperty("mail.transport.protocol", "smtp"); prop.setProperty("mail.smtp.auth", "true"); // 2、设置参数配置,开启session Session session = Session.getInstance(prop); session.setDebug(true); // 3、获取Transport并连接 Transport ts = session.getTransport(); ts.connect(host, username, password); // 4、创建Message Message message = createMail(session, user); // 5、发送Message ts.sendMessage(message, message.getAllRecipients()); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); }
}
/** * * createMail:创建要发送的邮件. <br/> * TODO(这里描述这个方法适用条件 – 可选).<br/> * TODO(这里描述这个方法的执行流程 – 可选).<br/> * TODO(这里描述这个方法的使用方法 – 可选).<br/> * TODO(这里描述这个方法的注意事项 – 可选).<br/> * * @author Dante Fung * @param session * @param user2 * @return * @throws Exception * @since JDK 1.6 */ private Message createMail(Session session, User user) throws Exception { MimeMessage message = new MimeMessage(session); // 设置发件人 message.setFrom(new InternetAddress(from)); // 设置收件人 message.setRecipient(Message.RecipientType.TO, new InternetAddress(user.getEmail())); // 设置邮件主题 message.setSubject("主题:用户注册邮件");
//String info = "恭喜你,注册成功!您的用户名为:" + user.getUsername() + ",您的密码为:" + user.getPassword() + ",请妥善保管,如有问题请联系网站客服!"; // 设置邮件正文 message.setContent(getInfo(), "text/html;charset=UTF-8"); // 保存邮件 message.saveChanges(); return message; }
public User getUser() { return user; }
public void setUser(User user) { this.user = user; }
public String getInfo() { return info; }
public void setInfo(String info) { this.info = info; }
} |
message.jsp <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>消息提示页面</title>
<meta http-equiv="pragma" content="no-cache"> <meta http-equiv="cache-control" content="no-cache"> <meta http-equiv="expires" content="0"> <!-- <link rel="stylesheet" type="text/css" href="styles.css"> -->
</head>
<body> ${msg } </body> </html> |
关于RFC:
RFC[一系列以编号排定的文件]:包含了关于Internet的几乎所有重要的文字资料。
Request For Comments(RFC),是一系列以编号排定的文件。文件收集了有关互联网相关信息,以及UNIX和互联网社区的软件文件。目前RFC文件是由Internet Society(ISOC)赞助发行。基本的互联网通信协议都有在RFC文件内详细说明。RFC文件还额外加入许多的论题在标准内,例如对于互联网新开发的协议及发展中所有的记录。因此几乎所有的互联网标准都有收录在RFC文件之中。
参考资料:
RFC 882 November 1983
Domain Names - Concepts and Facilities
mailbox names, they have also created an increasingly large and
irregular set of methods for identifying the location of a
mailbox. Some of these methods involve the use of routes and
forwarding hosts as part of the mail destination address, and
consequently force the mail user to know multiple address formats,
the capabilities of various forwarders, and ad hoc tricks for
passing address specifications through intermediaries.
These problems have common characteristics that suggest the nature
of any solution:
The basic need is for a consistent name space which will be
used for referring to resources. In order to avoid the
problems caused by ad hoc encodings, names should not contain
addresses, routes, or similar information as part of the name.
The sheer size of the database and frequency of updates suggest
that it must be maintained in a distributed manner, with local
caching to improve performance. Approaches that attempt to
collect a consistent copy of the entire database will become
more and more expensive and difficult, and hence should be
avoided. The same principle holds for the structure of the
name space, and in particular mechanisms for creating and
deleting names; these should also be distributed.
The costs of implementing such a facility dictate that it be
generally useful, and not restricted to a single application.
We should be able to use names to retrieve host addresses,
mailbox data, and other as yet undetermined information.
Because we want the name space to be useful in dissimilar
networks, it is unlikely that all users of domain names will be
able to agree on the set of resources or resource information
that names will be used to retrieve. Hence names refer to a
set of resources, and queries contain resource identifiers.
The only standard types of information that we expect to see
throughout the name space is structuring information for the
name space itself, and resources that are described using
domain names and no nonstandard data.
We also want the name server transactions to be independent of
the communications system that carries them. Some systems may
wish to use datagrams for simple queries and responses, and
only establish virtual circuits for transactions that need the
reliability (e.g. database updates, long transactions); other
systems will use virtual circuits exclusively.
关于Ad-hoc networking:
An IBSS (Independent Basic Service Set) network, often called an ad-hoc network, is a way to have a group of devices talk to each other wirelessly, without a central controller. It is an example of a peer-to-peer network, in which all devices talk directly to each other, with no inherent relaying.
For example, ad-hoc networking may be used to share an internet connection.
参考:
http://www.cnblogs.com/xdp-gacl/p/4216311.html
http://univasity.iteye.com/blog/1173296