[TOC]

0x00 简述前言

描述:最近正好在学邮件发信与激活的小程序开发,顺便把邮箱发信的基础知识理一理方便后续的开发工作以及安全相关的一些知识;

1.邮箱简述

Q:什么是邮件服务器?
答:它可以类似于现实生活中的邮局投递用户的邮件,电子邮件是Intenet基本服务之一,它主要负责接收用户投递过来的邮件,并把邮件投递到邮件接收者的电子邮箱,但是前提是需要在服务器上安装相应的邮件服务器应用; 使用电子邮件系统可以实现不受时间和空间限制的信息传递额交流;

  • 常见的邮件服务提供商: Sina , QQ , 163 / 126 , Foxmail, 189, aliyun ,Outlook,Gmail 等等;
  • 邮件服务器:按照提供的服务类型可以分为发送邮件的服务器接收邮件的服务器


Q:什么是电子邮箱?
答:即我们在邮件服务提供商或者说是邮件服务器上申请建立的一个账户(相当于收货地址),并且为每一个用户分配一定的空间用于保存发送的电子邮件和接收到的电子邮件;


Q:如何搭建自己的邮件服务器?需要那些技术?
答:这将是本文的核心思想,从下面的基础实例中的可以从搭建到使用。
邮件服务器需求:

  • 服务器
  • 邮件服务端(易邮)和客户端(foxmail)
  • 域名(DNS / A / MX / CNAME)

什么是 A 记录?

  • A (Address) 记录是用来指定主机名(或域名)对应的 IP 地址记录。用户可以将该域名下的主机名(二级域名)指向到自己的 服务器上。

什么是别名记录(CNAME)?

  • 也被称为规范名字。这种记录允许您将多个名字映射到同一台计算机。 通常用于同时提供 WWW 和 MAIL 服务的计算机。例如,有一台计算机名为“host.mydomain.com”(A记录)。 它同时提供 WWW 和 MAIL 服务,为了便于用户访问服务。可以为该计算机设置两个别名(CNAME):WWW 和 MAIL。 这两个别名的全称就是“www.mydomain.com”和“mail.mydomain.com”。实际上他们都指向“host.mydomain.com”。

什么是 MX 记录?(重点)

  • MX(Mail Exchanger)记录是邮件交换记录,它指向一个邮件服务器,用于电子邮件系统发邮件时根据收信人的地址后缀来定位邮件服务器。例如,当 Internet 上的某用户要发一封信给 [email protected] 时,该用户的邮件系统通过 DNS 查找 mydomain.com 这个域名的 MX 记录,如果 MX 记录存在, 用户计算机就将邮件发送到MX记录所指定的邮件服务器上。


Q:邮件协议的介绍:
描述:邮件协议作用是约定了邮件在网络中传输格式,便于接收发送邮件双方可以正常看到对方所发的信息(实际上是解码)
常用的邮件协议有两种(发送和接收):

发送:

  • SMTP 协议-发邮件协议,全称为 Simple Mail Transfer Protoco(简单邮件传输协议),它定义了邮件客户端软件与 SMTP
    服务器之间、以及两台 SMTP 服务器之间的通讯规则。
    • SMTP 协议属于 TCP/IP 协议簇,它帮助每台计算机在发送或中转信件时找到下一个目的地。SMTP 服务器就是遵循 SMTP 协议的发送邮件服务器。
    • SMTP 认证简单地说就是要求必须在提供了账户名和密码之后才可以登录 SMTP 服务器,增加 SMTP 认证的目的是为了使用户避免受到垃圾邮件的侵扰。
    • 端口: 465/994 , 25
      接收:
  • POP3 协议-收邮件协议,全称为Post Office Protocol(邮局协议)即第3个版本,它定义了邮件客户端软件与 POP3 服务器的通讯规
    则。

    • 它是因特网电子邮件的第一个离线协议标准,POP3允许用户从服务器上把邮件存储到本地主机(即自己的计算机)上,同时删除保存在邮件服务器上的邮件而POP3服务器则是遵循POP3协议的接收邮件服务器,用来接收电子邮件的。
    • 端口: 995, 110
  • IMAP 协议-交互式存取邮件协议,全称是 Internet Mail Access Protocol,它是跟POP3类似邮件访问标准协议之一。

    • 开启了IMAP后您在电子邮件客户端收取的邮件仍然保留在服务器上,同时在客户端上的操作都会反馈到服务器上,如:删除邮件,标记已读等,服务器上的邮件也会做相应的动作。所以无论从浏览器登录邮箱或者客户端软件登录邮箱,看到的邮件以及状态都是一致的。
    • 端口: 993, 143
      WeiyiGeek.协议端口信息

      WeiyiGeek.协议端口信息

总结:

  • 1.POP3 与 IMAP 的区别?

    POP3协议允许电子邮件客户端下载服务器上的邮件,但是在客户端的操作(如移动邮件、标记已读等),不会反馈到服务器上。
    IMAP协议提供与电子邮件客户端之间的双向通信,客户端的操作都会反馈到服务器上,对邮件进行的操作服务器上的邮件也会做相应的动作。同时它也像pop3支持邮件下载服务,让用户进行离线阅读; 它还提供的摘要浏览功能可以让你在阅读完所有的邮件到达时间、主题、发件人、大小等信息后才作出是否下载的决定,更好地支持了从多个不同设备中随时访问新邮件。
    总之IMAP 整体上为用户带来更为便捷和可靠的体验。POP3 更易丢失邮件或多次下载相同的邮件,但 IMAP 通过邮件客户端与webmail 之间的双向同步功能很好地避免了这些问题。

WeiyiGeek.区别对应

WeiyiGeek.区别对应


邮箱发送流程

WeiyiGeek.发送流程

WeiyiGeek.发送流程


2.MIME的编码

描述:说到邮件就不得不提到MIME的编码介绍(base64)及使用的意义

2.1 MIME: Multipurpose Internet Mail Extensions
描述:英国帝国大学计算机在线字典FOLDOC对MIME的解释为:多部分(multi-part)、多媒体电子邮件和WWW超文本的一种编码标准,用于传送诸如图形、声音和传真等非文本数据。MIME定义于RFC1341,用MIMENCODE的方法将二进制数据转换成为一种被称为BASE64的ASCII子集的字符的组合。

Internet上有专门讨论MIME的新闻组:comp.mail.mime

  • MIMENCODE最早称为MMENCODE提出用MIMENCODE代替UUENCODE,是因为UUENCODE使用了一些字符在一些邮件网关(特别是那些转换ASCII和EBCDIC码的网关)中造成传输障碍,(还有一些软件不能对所有 UUENCODE 的算法进行正确解码而导致邮件的阅读困难),因此 MIME 被设计用于替代UUENCODE,但是结果是这些协议共存。
  • 在MIME出台之前,使用RFC 822只能发送基本的ASCII码文本信息,邮件内容如果要包括二进制文件、声音和动画等,实现起来非常困难。MIME提供了一种可以在邮件中附加多种不同编码文件的方法,弥补了原来的信息格式的不足。实际上不仅仅是邮件编码,现在MIME经成为HTTP协议标准的一个部分 。

2.2 MIME编码方式简介
描述:对邮件进行编码最初的原因是因为 Internet 上的很多网关不能正确传输8bit内码的字符,比如汉字等。编码的原理就是把8bit的内容转换成7bit的形式以能正确传输,在接收方收到之后,再将其还原成8bit的内容。

在MIME协议之前,邮件的编码曾经有过UUENCODE等编码方式 ,但是由于MIME协议算法简单,并且易于扩展,现在已经成为邮件编码方式的主流,不仅是用来传输8bit的字符,也可以用来传送二进制的文件,如邮件附件中的图像、音频等信息,而且扩展了很多基于MIME 的应用。
从编码方式来说,MIME定义了两种编码方法Base64与QP(Quote-Printable)

  • 1.Base64编码
    描述:Base64是一种通用的方法,其原理很简单,就是把三个Byte的数据用4个Byte表示。在这四个Byte中,实际用到的都只有前面6bit,这样就不存在只能传输7bit的字符的问题了。Base64的缩写一般是B。
    Base64将输入的字符串或一段数据编码成只含有{‘A’-‘Z’, ‘a’-‘z’, ‘0’-‘9’, ‘+’, ‘/‘}这64个字符的串,’=’用于填充。
    其编码的方法是,将输入数据流每次取6bit,用此6bit的值(0-63)作为索引去查表,输出相应字符。
    这样每3个字节将编码为4个字符(3×8 → 4×6);不满4个字符的以’=’填充。
    有的场合,以“=?charset?B?xxxxxxxx?=”表示xxxxxxxx是Base64编码,且原文的字符集是charset。在段体内则直接编码,适当时机换行,MIME建议每行最多76个字符。
    Base64的算法很简单,它将字符流顺序放入一个24位的缓冲区,缺字符的地方补零。
    然后将缓冲区截断成为4个部分,高位在先,每个部分6位,用64个字符重新表示。如果输入只有一个或两个字节,那么输出将用等号“=”补足。这可以隔断附加的信息造成编码的混乱。

  • 2.QP编码
    描述:另一种方法是QP(Quote-Printable) 方法,通常缩写为“Q”方法,其原理是把一个8bit的字符用两个16进制数值表示,然后在前面加“=”。所以我们看到经过QP编码后的文件通常是这个样子:=B3=C2=BF=A1=C7=E5=A3= AC=C4=FA=BA=C3=A3=A1
    Quoted-printable根据输入的字符串或字节范围进行编码,若是不需编码的字符直接输出。若需要编码则先输出’=’后面跟着以2个字符表示的十六进制字节值。有的场合以“=?charset?Q?xxxxxxxx?=”表示xxxxxxxx是Quoted-printable编码,且原文的字符集是charset。在段体内则直接编码适当时机换行,换行前额外输出一个’=’。

2.3MIME的头信息
描述:邮件头在邮件头中有很多从RFC 822沿用的域名MIME也增加了一些。

常见的标准域名和含义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#域名                           含义               添加者
Received 传输路径 各级邮件服务器
Return-Path 回复地址 目标邮件服务器
Delivered-To 发送地址 目标邮件服务器
Reply-To 回复地址 邮件的创建者
From 发件人地址 邮件的创建者
To 收件人地址 邮件的创建者
Cc 抄送地址 邮件的创建者
Bcc 暗送地址 邮件的创建者
Date 日期和时间 邮件的创建者
Subject 主题 邮件的创建者
Message-ID 消息ID 邮件的创建者
MIME-Version MIME版本 邮件的创建者
Content-Type 内容的类型 邮件的创建者
Content-Transfer-Encoding 内容的传输编码方式 邮件的创建者

#非标准的、自定义域名都以X-开头,例如X-Mailer, X-MSMail-Priority等,通常在接收和发送邮件的是同一程序时才能理解它们的意义。
段头
#在段头中,大致有如下一些域:
#域名 含义
Content-Type 段体的类型
Content-Transfer-Encoding 段体的传输编码方式
Content-Disposition 段体的安排方式
Content-ID 段体的ID
Content-Location 段体的位置(路径)
Content-Base 段体的基位置
有的域除了值之外,还带有参数。值与参数、参数与参数之间以“;”分隔。参数名与参数值之间以“=”分隔。

  • 1.MIME-Version: 表示使用的MIME的版本号,一般是1.0;如:MIME-Version: 1.0
  • 2.Content-Type: 定义了正文的类型(“主类型/子类型”的形式),我们实际上是通过这个标识来知道正文内是什么类型的文件。比如text/plain 表示的是无格式的文本正文,text/html 表示的 Html 文档,image/gif 表示的是 gif 格式的图片等等。

    • 主类型有text, image, audio, video, application, multipart, message等,分别表示文本、图片、音频、视频、应用、分段、消息等。
    • 子类型:每个主类型都可能有多个子类型,如text类型就包含plain, html, xml, css等子类型
    • Tips:以X-开头的主类型和子类型,同样表示自定义的类型,未向IANA正式注册,但大多已经约定成俗了。如application/x-zip-compressed是ZIP文件类型。
    • Tips:在Windows中注册表的”HKEY_CLASSES_ROOT/MIME/Database/Content Type”内列举了除multipart之外大部分已知的Content-Type。
  • 3.关于参数的形式,RFC里有很多补充规定,有的允许带几个参数,较为常见的有:

    1
    2
    3
    4
    5
    主类型           参数名           含义
    text charset 字符集
    image name 名称
    application name 名称
    multipart boundary 边界
  • 4.multipart类型:邮件中常用到的复合类型,该类型表示正文是由多个部分组成的,后面的子类型说明的是这些部分之间的关系。
    邮件中用到的三个类型有:

    • (1).multipart/alternative:表示正文由两个部分组成,可以选择其中的任意一个。主要作用是在征文同时有text格式和html格式时,可以在两个正文中选择一个来显示,支持 html 格式的邮件客户端软件一般会显示其 HTML 正文,而不支持的则会显示其Text正文;
    • (2).multipart/related:表示文档的多个部分是相关的,一般用来描述 Html 正文与其相关的图片。
    • (3).multipart/mixed:表示文档的多个部分是混合的,指正文与附件的关系。如果邮件的MIME类型是multipart/mixed,即表示邮件带有附件。
    • Tips:multipart类型是MIME邮件的精髓。邮件体被分为多个段,每个段又包含段头和段体两部分,这两部分之间也以空行分隔。
      它们之间的层次关系可归纳为下图所示:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      +------------------------- multipart/mixed ----------------------------+
      | |
      | +----------------- multipart/related ------------------+ |
      | | | |
      | | +----- multipart/alternative ------+ +----------+ | +------+ |
      | | | | | 内嵌资源 | | | 附件 | |
      | | | +------------+ +------------+ | +----------+ | +------+ |
      | | | | 纯文本正文 | | 超文本正文 | | | |
      | | | +------------+ +------------+ | +----------+ | +------+ |
      | | | | | 内嵌资源 | | | 附件 | |
      | | +----------------------------------+ +----------+ | +------+ |
      | | | |
      | +------------------------------------------------------+ |
      | |
      +----------------------------------------------------------------------+
      补充总结:从层次关系中可以看出,如果在邮件中要添加附件必须定义multipart/mixed段;如果存在内嵌资源至少要定义multipart/related段;如果纯文本与超文本共存至少要定义multipart/alternative段。

什么是“至少”?
答:如果只有纯文本与超文本正文,那么在邮件头中将类型扩大化,定义为multipart/related,甚至multipart/mixed,都是允许的。
multipart诸类型的共同特征是,在段头指定“boundary”参数字符串,段体内的每个子段以此串定界。所有的子段都以--+boundary行开始,父段则以--+boundary+--行结束。段与段之间也以空行分隔。在邮件体是multipart类型的情况下,邮件体的开始部分(第一个“–” +boundary行之前)可以有一些附加的文本行,相当于注释,解码时应忽略。段间也可以有一些附加的文本行,不会显示出来。

些复合类型又是可以嵌套使用的,比如说一个带有附件的邮件,同时有html与text两种格式的正文,则邮件的结构是:

1
2
3
4
5
6
7
8
9
Content-Type: multipart/mixed
部分一:

Content Type : multipart/alternative:
Text 正文;
Html 格式的正文 
部分二:
附件
邮件结束符;

由于复合类型由多个部分组成,因此需要一个分隔符来分隔这多个部分,这就是上面的邮件源文件中的boundary所描述的,对于每一个Contect type :multipart/* 的内容,都会有这么一个说明,表示多个部分之间的分隔。

含有 MIME/BASE64编码的邮件,你查看它的源码时一般都含有:“This is a multi-part message in MIME format.”这样的句子。也可以被绝大多数的email程序进行解码,包括Netscape、MS Mail、Eudora等。这些程序可以正确识别邮件的正文,恢 MIME/BASE64 编码的部分为正确的文字或夹带的二进制文件。

  • 5.Content-Transfer-Encoding
    它表示了这个部分文档的编码方式。只有识别了这个说明才能用正确的解码方式实现对其解码。

    • Content-Transfer-Encoding共有Base64, Quoted-printable, 7bit, 8bit, Binary等几种。
    • 其中7bit是缺省的编码方式。电子邮件源码最初设计为全部是可打印的ASCII码的形式。
    • 非ASCII码的文本或数据要编码成要求的格式。
    • Base64, Quoted-Printable是在非英语国家使用最广使的编码方式。
    • Binary方式只具有象征意义,而没有任何实用价值。
  • 6.boundary

    • 这个分隔符是正文中不可能出现的一串古字符的组合,在文档中,以”–”加上这个boundary 来表示一个部分的开始,在文档的结束,以”–”加boundary再在最后加上”–”来表示文档的结束。由于复合类型是可以嵌套使用的因此邮件中可能会多个boundary。


3.邮件原始信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#(1) 从那里发信到那里接收以及接收信息的收信人
Received: from 发信邮件服务器 (unknown [115.124.23.89])
by 收信邮件服务器 (NewMx) with SMTP id
for <收信人>; Fri, 05 Jun 2020 10:56:48 +0800
#(2) QQ相关的参数
X-QQ-FEAT: Nrj7KsauvmTPZQ**********Du8ZT3+WQp6dDEfYY=
X-QQ-MAILINFO: Nj4k/6lmmRCSyBQ**********/EB9mnO1izsI+o1HWNTKN+0+IWP
X-QQ-mid: mxsza48t时间戳-收信时间tfqsxubj9
X-QQ-ORGSender: [email protected]
X-QQ-XMAILINFO: NlBzqDjet/+1*********/lm+frv3kDP7DnqqWKEAAiP9c
DKIM-Signature:v=1; a=rsa-sha256; c=relaxed/relaxed;
d=newsletter.aliyun.com; s=s1024;
t=时间戳-收信时间; h=Date:From:To:Message-ID:Subject:MIME-Version:Content-Type;
bh=wfxerPzsEEmDRDRXjMJBI13r4XrE/SOsQJOnJYilKV8=;
b=LwKBc0owsr5zgBDFwwpZl3DadzCmLooo17myWuCBjkfWRUGVPXnQ7w2idnLigyemJe0nXeZC4CVzxRUhXBbqy+8piGBYOgBTCs+TLAorsbek0DbnFmY/5psSHTzAAbXWBmKiPhklr0FYNcD7HTpXxHi6/4meSe5rNmJRGiDmPh8=
X-EnvId: 106163880073
# (3) 接收到谁的发信
Received: from mscchannel011175060107.eu13(mailfrom:[email protected] fp:SMTPD_----.Wrr2.i)
by smtp.aliyun-inc.com(127.0.0.1);
Fri, 05 Jun 2020 10:56:47 +0800
Date: Fri, 5 Jun 2020 10:56:47 +0800 (CST)
From: =?UTF-8?B?6Zi/6YeM5LqR?= <[email protected]>
To: 收信人
Message-ID: 2710120060500404702
# (4) 主题base64之UTF-8编码
Subject: =?UTF-8?B?54mp6IGU572R5bmz5Y+w5bey5pSv5oyBU0RL5Zyo57q/6KOB5Ymq?=

# (5) 前面我们所说的版MIME本
MIME-Version: 1.0

# (6) 文档类型:表示文档的多个部分是混合的,指正文与附件的关系
Content-Type: multipart/mixed;
boundary="----=_Part_2824562_163938721.时间戳-收信时间439"

# (7) 文件开始标记
------=_Part_2824562_163938721.时间戳-收信时间439

# (8) 包含文档类型
Content-Type: text/html;charset=utf-8

# (9) 文档传输编码采用QR
Content-Transfer-Encoding: quoted-printable

# (10) 文件正文 QR 以 十六进制的ASCII码表示
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.=
w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns=3D"http://www.w3.org/1999/xhtml">
=09<head>
=09=09<meta http-equiv=3D"Content-Type" content=3D"text/html; charset=3Dutf=
-8" />
=09=09<title>=E9=98=BF=E9=87=8C=E4=BA=91=E5=AE=98=E7=BD=91=E7=BB=9F=E4=B8=
=80=E4=B8=9A=E5=8A=A1=E9=82=AE=E4=BB=B6=E6=A0=B7=E5=BC=8F(=E4=B8=AD=E6=96=
=87_=E4=B8=AD=E5=9B=BD=E7=AB=99)</title>
=09</head>
=09<img src=3D"http://ac.mmstat.com/aliyun.12.1?logtype=3D4&type=3Demail&ms=
gid=3D2710120060500404702&areaid=3Dcn&siteId=3Dcn"/></body>
</html>

# (11) 文档结束标准
------=_Part_2824562_163938721.时间戳-收信时间439--

0x01 易邮局域网邮件服务器

环境说明:

1
2
3
- Windows 7
- 服务端:eyoumailserversetup.exe
- 客户端:foxMail

安装流程:
Step1.运行邮件服务器软件通常一直下一步即可安装完成,完成后打开C:\EyouMailServer\MailServer.exe界面如下:

WeiyiGeek.步骤1

WeiyiGeek.步骤1

Step2.工具->系统设置->邮箱域名设置(weiyigeek.top)

WeiyiGeek.步骤2

WeiyiGeek.步骤2

Step3.添加新账号设置并设置密码,利用foxmail进行添加账号登陆;

WeiyiGeek.步骤3

WeiyiGeek.步骤3


0x02 邮件服务测试

1.Telnet

测试搭建的邮件服务:

  • 方式1:Telnet测试发信

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    # (1)连接邮件服务器smtp端口
    telnet 10.20.172.110 25

    #220 ESMTP 易邮邮件服务器 5.2.2004.02.18 SMTP Service Ready

    # (2)helo和ehlo指令的作用是向服务器标示用户身份,返回邮件服务器身份;
    ehlo weiyigeek.top
    250-AUTH=LOGIN
    250 AUTH LOGIN

    # (3)验证用户使用auth login进行验证时用户名和密码是经过base64编码过后的字符
    auth login
    334 VXNlcm5hbWU6
    YWRtaW4=
    334 UGFzc3dvcmQ6
    d2VpeWlnZWVr
    235 OK # 表明验证成功

    # (4)使用mail指令写一个邮件信息
    mail from:<[email protected]>
    250 OK
    rcpt to:<[email protected]> #注意这里多个rcpt指令格式
    250 OK

    # (5)使用data指令开始写邮件内容
    data
    354 send the mail data, end with .

    # (6)邮件信息头与正文
    from:<[email protected]>
    to:<[email protected]>
    subject:Telnet send Email

    Telnet smtp 25 port

    # (7) 结束data指令的标志
    .
    250

    # (8) 退出Telnet邮件服务器
    quit
    221 SMTP SERVICE CLOSED
    WeiyiGeek.telnet

    WeiyiGeek.telnet

  • 方式2:Telnet pop3 邮件查看 (常用此方法在内网中进行暴力破解)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    $telnet 10.20.172.110 110
    Trying 10.20.172.110...
    Connected to 10.20.172.110.
    Escape character is '^]'.
    +OK 5.2 POP3 Service Ready

    # (1) pop 用户
    user zw
    +OK welcome here
    # (2) pop 用户密码
    pass 123456
    +OK
    # (3) 查看邮件信息可以这里看到4封邮件349212字节
    stat
    +OK 4 349212
    # (4) list指令列出所有的邮件
    list
    +OK 4 349212
    1 445
    2 345356
    3 3116
    4 295

    # (5) 删除用dele+序号删除邮件(只是标记删除,退出后真的删除),
    dele 1
    +OK


    # (6) 用rset恢复标记的邮件
    rset
    +Ok

    # (7) retr+序号查看邮件内容。
    retr 2
    +OK 345356 octets

    # (8) 邮件正文信息
    Return-Path: <[email protected]>
    Received: from WeiyiGeek (unknown [10.20.172.103])
    by weiyigeek.top with CMailServer 5.2 SMTP; Thu, 11 Jun 2020 06:32:25 +0800
    Date: Wed, 10 Jun 2020 22:33:12 +0800
    From: "[email protected]" <[email protected]>
    To: weiyigeek <[email protected]>
    Cc: zw <[email protected]>
    Subject: =?GB2312?B?suLK1Lei0MU=?=
    X-Priority: 3
    X-Has-Attach: no
    X-Mailer: Foxmail 7.2.11.303[cn]
    Mime-Version: 1.0 #信息头讲解参考上面
    Message-ID: <[email protected]>
    Content-Type: multipart/related;
  • 方式3.采用邮箱客户端进行登陆发信,这里不多说了(自己建的邮件服务器采用密码,如果用的是QQ采用授权码即可);


3.javamail.jar

描述:老规矩我们首先需要将mail包导入到工程之中,此处采用javamail-1.4.4版本和类型;

基础示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package top.weiyigeek.email;

import java.util.Properties;

import javax.mail.Authenticator;
import javax.mail.Message;
import javax.mail.Message.RecipientType;
import javax.mail.MessagingException;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

import org.junit.Test;

public class emailDemo {

@Test
public void sendMail() throws AddressException, MessagingException {
//1.服务器的设置
Properties props = new Properties();
props.setProperty("mail.host", "10.20.172.110");
props.setProperty("mail.smtp.auth","true");

//2.邮箱用户账号和密码
Authenticator authenticator = new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication("[email protected]", "weiyigeek");
}
};

//3.建立联系返回session
Session session = Session.getDefaultInstance(props, authenticator);

//4.邮件编写与发送
//4.1 邮件编写
Message message = new MimeMessage(session);
//4.2 发件人信息
message.setFrom(new InternetAddress("[email protected]"));
//4.3 收件人 , to:收件人 cc:抄送 bcc:暗送(密送)。(模拟账号)
message.setRecipient(RecipientType.TO, new InternetAddress("[email protected]"));
//4.4 主题
message.setSubject("Java-Mail测试邮件信息....");
//4.5 内容
message.setContent("<h1 style='color:red'>测试发信信息</h1>", "text/html;charset=UTF-8");

//5.将信息进行发送:Transport
Transport.send(message);
}
}

执行结果:(内部测试)

1
2
3
[2020-06-11 18:30:40] SMTP 10.20.172.103 ESMTP authentication: [email protected]
[2020-06-11 18:30:40] SMTP 10.20.172.103 mail from [email protected] to [email protected] successfully
[2020-06-11 18:30:59] SMTP MailUndeliverable mail from admin to [email protected] successfully

WeiyiGeek.测试

WeiyiGeek.测试

实际案例:
利用了RedisPool+javamail实现用户发信与激活;

邮件激活发信页面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<title>Email邮箱验证测试</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css">
<script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script type="text/javascript">
$(function(){
$("#getCode").click(function(){
var email = $("#email").val();
console.log(email);
$.get("/Web/UserOper?method=activeEmail&email="+email,function(data,status){
console.info(data);
})
});
});
</script>
</head>
<body>
<div class="container" id="activeemail">
<h1> 邮箱激活 </h1>
<form class="form-horizontal" role="form" action="UserOper?method=vertyEmail">

<label lass="col-sm-2 control-label" for="email">邮箱地址:</label>
<div class="form-group">
<div class="col-sm-6">
<input type="text" class="form-control" id="email" value="[email protected]" disabled>
</div>
</div>

<label lass="col-sm-2 control-label" for="emailcode">邮箱验证码:</label>
<div class="form-group">
<div class="col-sm-4">
<input type="text" class="form-control" id="emailcode" placeholder="请输入验证码" name="emailcode">
</div>
<div class="col-sm-2">
<input type="button" class="btn btn-default" value="获取验证码" id="getCode">
</div>
</div>
<button type="submit" class="btn btn-primary">提交验证</button>
</form>
</div>
</body>
</html>

请求部分servlet方法(采用反射进行实现):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/Web/src/top/weiyigeek/baseservlet/BaseServlet.java
/***
* @author WeiyiGeek
* @desc 工具类:自建一个Servlet基础类所有继承该类的Servlet都将执行重写后的Service方法
*/
public class BaseServlet extends HttpServlet {
@SuppressWarnings("unused")
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("BaseServlet - 被继承的基础Servlet中的Service方法");
//1.获取请求的操作的方法
String method = req.getParameter("method");
//2.定义转发路径
String redirection = null;
//3.获取当前的字节码对象
Class<? extends BaseServlet> clazz = this.getClass(); //此时获取的实际上是继承者的class即 UserOper.class
try {
//4.采用反射reflect形式获取到clazz的方法并且进行调用传入的指定方法
Method md = clazz.getMethod(method, HttpServletRequest.class, HttpServletResponse.class);
if (null != md) {
redirection = (String) md.invoke(this, req, resp);
}
//5.当跳转路径不为空的时候返回的指定路径
if (null != redirection){
req.getRequestDispatcher(redirection).forward(req, resp);
}

}catch (Exception e) {
e.printStackTrace();
}
}
}


/Web/src/top/weiyigeek/baseservlet/UserOper.java
public class UserOper extends BaseServlet {
private static final long serialVersionUID = 1L;
public UserOper() {
super();
System.out.println("UserOper Servlet 构造方法!");
}
public String activeEmail(HttpServletRequest request,HttpServletResponse response) throws ServerException {
System.out.println("邮件发信");
return "/SendMail";
}
}

注册邮件发送servlet:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package top.weiyigeek.baseservlet;
import java.io.IOException;
import java.util.Random;
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 redis.clients.jedis.Jedis;
import top.weiyigeek.connredis.RedisDemo3;
import top.weiyigeek.email.Email;
import top.weiyigeek.utils.RedisPoolUtil;

public class SendMail extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1.返回类型
response.setContentType("application/json;charset=utf-8");
//2.redis相关
Jedis jedis = RedisPoolUtil.getJedis();

//3.客户端session
HttpSession session = request.getSession();
String sessionid = session.getId();

//4.获取得到的email(生成激活码)
String email = request.getParameter("email");
String emailcode = String.format("%04d", new Random().nextInt(10000));
String context=" <h3 style='color:grade'>欢迎注册WeiyiGeek博客</h3><br><b>邮箱激活验证码:"+emailcode+"</b>";

//5.判断是否激活邮箱否则发信
if(!RedisDemo3.exits(jedis, sessionid) && !RedisDemo3.exits(jedis, email)) {
System.out.println("session:"+sessionid+" emailcode:"+emailcode);
try {
RedisDemo3.setKeyString(jedis, sessionid, emailcode, 360);
RedisDemo3.setKey(jedis, email, "false");
try {
Email.sendMail(email, "[注册码] weiyigeek Blog's", context);
response.getWriter().append("{\"status\":\"successful\",\"msg\":\"发信成功\"}");
} catch (Exception e) {
response.getWriter().append("{\"status\":\"Error\",\"msg\":\"发信失败,请重试!\"}");
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
jedis.close();
}
}else if (!RedisDemo3.exits(jedis, sessionid) || !RedisDemo3.getKeyString(jedis, email).equals("true")) {
System.out.println("session:"+sessionid+"--激活码:"+emailcode);
try {
Email.sendMail(email, "[注册码] weiyigeek Blog's", context);
response.getWriter().append("{\"status\":\"successful\",\"msg\":\"再次发信成功\"}");
} catch (Exception e) {
response.getWriter().append("{\"status\":\"Error\",\"msg\":\"再次发信失败,请重试!\"}");
}

} else {
System.out.println("session:"+sessionid);
response.getWriter().append("{\"status\":\"error\",\"msg\":\"mail already active and verity!\"}");
}

//7.使用完毕后记得关闭redis连接并将其返还给资源池
jedis.close();
}
}

执行结果:

1
2
3
邮件发信
当前Redis连接池被使用的数量: 1
session:0327E936C1BB849C79410E5BEA3D55ED emailcode:8537

WeiyiGeek.示例

WeiyiGeek.示例