前言
现代化的Web应用为了保证用户登录凭据的可靠性,通常会将验证真实性的验证码或验证链接等通过用户提供的渠道发送给用户,以验证其真实性。
所以,为了控制用户登录凭据,我们的产品经理决定采用用户验证机制。其实综合说来,现在被广泛采用的用户验证手段不外乎SMS短信验证(文字验证、语音验证、用户发送短信)(用于当用户提供手机号码作为登录凭据时)、邮箱链接、邮箱验证码(用于当用户提供邮箱账号作为登录凭据时)。而我们的项目因为比较初级,所以在成本上的预算几乎为零(必要时倒贴钱的那种QAQ)。为此我们必须找到一个价格实惠又容易操作的方法。
经过我的调查,目前国内市场上较受欢迎的SMS提供商,例如阿里云、SendCloud等,有着繁琐的个人验证审核的流程、高昂的费用、诸多的限制以及庞大的SDK,这其中每一项都足以令我们头疼。而邮件服务器虽说价格便宜了不少,而且还有腾讯企业邮件这种服务,但是操作过程也十分复杂。于是我想到在我们自己的服务器上架设一个邮件服务,虽然配置比较麻烦,但一免费,二没有限制,三纯净,去商业化,看起来非常划算,所以我就开始搭建我们自己的邮件服务。
在这个过程中碰到了几个坑,但是好在这些坑都已经有前人填过了,所以并不是十分坎坷。但是仍然有记录的必要,因为网上的文章大都很零散,而本文则记录从零开始假设可以使用的邮件服务,并会附上Python代码。
架设邮件服务的过程
1. 理论基础
以下图片较为直观地展现了邮件服务的工作原理及主要步骤:

邮件的发送和接收过程主要分为3步。
(1)当用户需要发送电子邮件时,首先利用客户端的电子邮件应用程序按规定格式起草、编辑一封邮件,指明收件人的电子邮件地址,然后利用SMTP将邮件送往发送端的邮件服务器。
(2)发送端的邮件服务器接收到用户送来的邮件后,接收件人地址中的邮件服务器主机名,通过SMTP将邮件送到接收端的邮件服务器,接收端的邮件服务器根据收件人地址中的账号将邮件投递到对应的邮箱中。
(3)利用POP3协议或IMAP,接收端的用户可以在任何时间、地址利用电子邮件应用程序从自己的邮箱中读取邮件,并对自己的邮件进行管理。
与邮件服务相关的协议主要有:SMTP、POP、IMAP,其中SMTP用于发送邮件,相当于邮局的接收部门;POP和IMAP都是用于接收邮件的,相当于专门前来取件并投递给用户的邮递员。
2. 软件安装和配置
- 操作系统:CentOS 6.9 i386
- 主机:海外BandWagon主机
- 安装方式:YUM + RPM
我打算使用Postfix作为默认的SMTP服务器,Dovecot作为默认的POP3/IMAP服务器,不使用SSL/TLS。
2.1 卸载Sendmail
有些服务器提供商的操作系统模板中可能自带了Sendmail,所以为了防止与Postfix发生冲突,首先要卸载Sendmail
yum remove sendmail
2.2 安装Postfix和Dovecot
yum install postfix dovecot -y
安装完毕后设置邮件传输代理(MTA):
alternatives --config mta
回车两次,再执行:
alternatives --display mta
如果第一行输出类似于mat - status is manual.
的语句即表示配置成功。
2.3 配置Postfix
Postfix的配置文件在/etc/postfix/
下,主要是main.cf
文件,以下代码的注释即为对该文件采取的修改:
vi /etc/postfix/main.cf #vi编辑postfix配置文件
#找到如下配置项酌情修改
######
#postfix主机名,修改成你的域名 此项需要添加A记录并指向postfix所在主机公网IP
myhostname = mail2.lenconda.top
#域名
mydomain = mail2.lenconda.top
#本机postfix的邮箱域名后最 此项默认值使用myhostname
#此处使用了前项mydomain 也就是说本机postfix邮箱后缀为:@mail2.lenconda.top
myorigin = $mydomain
#指定postfix系统监听的网络接口 此处必须是localhost或127.0.0.1或内网ip
#若注释或填入公网ip 服务器的25端口将对公网开放
#默认值为all 即监听所有网络接口
inet_interfaces = all
#网络协议 这里ipv4即可
inet_protocols = ipv4
#指定postfix接收邮件时收件人的域名,换句话说,也就是你的postfix系统要接收什么样的邮件。
#此项配置中$myhostname表示postfix接受@$myhostname为后缀的邮箱的邮件 逗号分割支持指多项
#此项默认值使用myhostname
mydestination = $mydomain, localhost.$mydomain, localhost
#此项制定接收邮件的规则 可以是hash文件 此项对本次配置无意义 可以直接注释
local_recipient_maps =
#指定你所在的网络的网络地址
mynetworks = #加上一个公网IP, 10.200.9.xxx, 127.0.0.1
#请依据实际情况修改
#指定MUA通过smtp连接postfix时返回的header头信息
#原始配置附带有postfix版本号 去掉即可,此项酌情处理,可以先不管
smtpd_banner = Lenconda ESMTP Server
下面的配置默认是没有的,需要在配置文件最后加上
#SMTP Config
#指定postfix兼容MUA使用不规则的smtp协议--主要针对老版本的outlook 此项对于本次配置无意义
broken_sasl_auth_clients = yes
#指定可以向postfix发起SMTP连接的客户端的主机名或ip地址
smtpd_client_restrictions = permit_sasl_authenticated
#此处permit_sasl_authenticated意思是允许通过sasl认证(也就是smtp链接时通过了账号、密码效验的用户)的所有用户
smtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated,
#发件人在执行RCPT TO命令时提供的地址进行限制规则 此处照搬复制即可reject_unauth_destination
#指定postfix使用sasl验证 通俗的将就是启用smtp并要求进行账号、密码效验
smtpd_sasl_auth_enable = yes
#指定SMTP认证的本地域名 本次配置可以使用 smtpd_sasl_local_domain = '' 或干脆注释掉 默认为空
smtpd_sasl_local_domain = $mydomain
#取消smtp的匿名登录 此项默认值为noanonymous smtp若能匿名登录危害非常大 此项请务必指定为noanonymous
smtpd_sasl_security_options = noanonymous
#指定通过postfix发送邮件的体积大小 此处表示5M
message_size_limit = 5242880
######
2.4 配置Dovecot
[root@localhost ~]# vim /etc/dovecot/dovecot.conf
# 26行: 如果不使用IPv6,请修改为*
listen = *
[root@localhost ~]# vim /etc/dovecot/conf.d/10-auth.conf
# 9行: 取消注释并修改
disable_plaintext_auth = no
# 97行: 添加
auth_mechanisms = plain login
[root@localhost ~]# vim /etc/dovecot/conf.d/10-mail.conf
# 30行: 取消注释并添加
mail_location = maildir:~/Maildir
[root@localhost ~]# vim /etc/dovecot/conf.d/10-master.conf
# 88-90行: 取消注释并添加
# Postfix smtp验证
unix_listener /var/spool/postfix/private/auth {
mode = 0666
user = postfix
group = postfix
}
配置文件视版本不同而略有差别。
2.5 配置sassdb
#提供smtp的虚拟账户和密码服务 sasldb2包含在saslauthd当中
yum -y install cyrus-sasl cyrus-sasl-lib cyrus-sasl-plain cyrus-sasl-devel
#当前mta查看
alternatives --display mta
#设置mta
alternatives --set mta /usr/sbin/sendmail.postfix
#再次查看mta
alternatives --display mta
#输出结果最后一行会有类似如下的提示:mta即设置完毕
#Current `best' version is /usr/sbin/sendmail.postfix.
3. 服务用户权限配置
在系统中新建一个用户,该用户可以发送接收邮件:
useradd noreply
password noreply
在sasldb中建立SMTP用户密码:
#配置postfix启用sasldb2作为smtp的账号秘密效验方式
#编辑通过sasl启用smtp账号密码效验的配置
vim /etc/sasl2/smtpd.conf #vi写入或编辑内容如下:
#####
pwcheck_method: auxprop
auxprop_plugin: sasldb
mech_list: plain login CRAM-MD5 DIGEST-MD5
#####
#这里需要注意的是:这个配置文件的位置是64位机器上的,32位机器应该在:/usr/lib/sasl2/smtpd.conf
#创建smtp账号
saslpasswd2 -c -u `postconf -h mydomain` test #回车会要求输入密码,连续两次
#表示创建test@$mydomain的邮箱账号(也是smtp的账号)和密码
#本例就是创建test@mail2.lenconda.top账号和密码
#此处注意的是smtp登录用的账号并不是单纯的用户名 而是整个邮箱地址字符串
#假设此处设置的smtp账号test@mail2.lenconda.top密码为test123 下方测试时要用到
#查看sasldb2的用户和密码
sasldblistusers2
#此命令进用户查看sasldb的用户情况
#此命令回车后会输出诸如这样的内容:test@mail2.lenconda.top: userPassword
#更改sasldb2数据的权限,让postfix可以读取
chmod 755 /etc/sasldb2
4. 启动服务
启动服务:
service postfix start
service dovecot start
添加自启动:
chkconfig postfix on
chkconfig dovecot on
开放相应的端口,在SELinux修改好恰当的配置。
在Python中测试
后端采用smtplib库来发送邮件,不接收邮件。以下为真实的代码(密码已做处理):
#!/usr/bin/python3
#coding: utf-8
import smtplib
from email.mime.text import MIMEText
from email.header import Header
import random
sender = 'noreply@mail2.lenconda.top'
receiver = 'lenconda@163.com'
subject = '验证用于 Petshow 注册'
smtpserver = 'mail2.lenconda.top'
username = 'noreply'
password = '${PASSWORD}'
code = random.randint(1000, 9999)
message = '''
<p>以下是您用于验证并注册 Petshow 及其服务的验证码:</p>
<br/>
<h3><b>''' + str(code) + '''</b></h3>
<br/>
<p>请注意,本验证码永久有效,但仅限使用一次。请勿将此验证码告知他人。除非您确认这不是您本人的操作,请不要忽略此邮件。</p>
<br/>
<b>此致!</b>
<br/>
<p>Petshow 团队敬上。</p>
'''
msg = MIMEText( message, 'html', 'utf-8' )
msg['Subject'] = Header( subject, 'utf-8' )
msg['From'] = Header("Petshow <noreply@mail2.lenconda.top>", 'utf-8')
smtp = smtplib.SMTP()
smtp.connect( smtpserver )
smtp.login( username, password )
smtp.sendmail( sender, receiver, msg.as_string() )
smtp.quit()
效果:
