基于 python + SendCloud 的邮箱认证

来源:互联网 发布:实木床 品牌 知乎 编辑:程序博客网 时间:2024/06/10 18:52

基于 python + SendCloud 的邮箱认证

一、实验简介

为了方便日后密码遗忘进行修改或者进行消息推广等,现在各大网站在注册的时候总是免不了填写邮箱验证邮箱的流程。

一定有人好奇通过链接进行邮箱认证背后的原理是什么,事实上它的原理非常简。

本门课程将通过使用 SendCloud 提供的 API 来实现一个本地版的邮箱认证流程。

1.1 课程知识点

通过本次课程的学习,我们将接触到以下知识:

  • 使用 SendCloud 的邮件 API 给指定邮箱发送邮件。
  • 使用 MySQLdb 模块对 mysql 数据库进行操作。
  • 使用 Requests 模块发送 HTTP POST 请求。
  • 使用 Flask 框架实现简单的网页后台。
  • 使用 hashlib 模块实现对指定字符串的加密处理。
  • 使用 random 和 string 模块生成指定长度的随机字符串。

注意:本次课程主要使用语言是 Python 2.7,如果没学过 Python 的同学可以先移步到课程《Python快速教程》学习一些基础的 Python 语法,再回来继续进行本课程。

1.2 实验主要流程

  1. 介绍邮箱验证原理。
  2. 注册 SendCloud。
  3. 配置数据库 MySQL。
  4. 编写后台程序。
  5. 编写前端程序。

1.3 所需安装模块

需要安装 Flask, requests 和 MySQLdb 模块。

$ sudo pip install flask
$ sudo pip install requests

安装 MySQLdb 模块,流程如下:

// 在编译安装 MySQL-python 前,先自动安装相关的编译环境$ sudo apt-get build-dep python-mysqldb// 安装 MySQL-python$ pip install MySQL-python

注意:以上安装流程仅适用于实验楼虚拟机环境,若在个人电脑上进行配置,请按照个人实际情况选择安装。

1.4 效果截图

最终效果如下图,因为主要是为了学习 SendCloud 的邮件 API ,以效率为先,界面就做的很简单了,大家在学习过程中完全可以按照自己的想法去实现登录界面,然后截图到实验报告里。

此处输入图片的描述

填写所需信息之后点击提交。

此处输入图片的描述

数据库生成相应记录, verificatin_status 一栏数值为 0 表示还未验证。

此处输入图片的描述

在 SendCloud 的管理界面中查看邮件投递状态。

此处输入图片的描述

验证邮件一般在一分钟之内能送达邮箱,如果长时间没有收到提示可以查看一下垃圾邮件。

此处输入图片的描述

复制链接输入到实验楼虚拟机浏览器中进行访问,完成认证。

此处输入图片的描述

此时数据库中 verificaitn_status 的值也变为 1 ,这表明该用户邮件已进行验证。

此处输入图片的描述

二、邮箱验证基本原理

邮箱认证的原理其实很简单,基本流程如下:

  1. 用户在表单页面上填写了表单,点击提交的时候通过 HTTP POST 方法将这些信息发送给后台程序,后台接受这些信息并将其存储到数据库中。
  2. 由第一步我们已经获得了用户的一些基本信息,如用户名和邮箱等,然后就可以调用 SendCloud 的邮件 API 给指定用户邮箱发送验证邮件。验证的链接格式一般为 http://domain.com/verify?token=xxxxxx&authcode=xxxxxx ,其中 token 是每个用户的唯一标识, authcode 是后台随机生成的验证码。
  3. 当用户点击验证链接的时候,浏览器对该链接进行访问,后台程序可以通过 HTTP GET 方法获取到链接中 token 和 authcode 两个参数的值,然后利用这个值对数据库进行查询,如果查询得到结果,则邮件验证成功,将数据库中相应的 verificatin_status 的值进行更新为 True

当然这只是一个验证的基本思想,验证过程的安全问题以及其多用户情况下数据库操作的互斥等问题都需要更深入进行考虑的,但是我们这门课主要是学习使用 SendCloud 的邮件 API,因此只需要实现一个这样的基本流程就行。

另外综上所述, token 只要满足唯一性就行了,可以是很简单的数字,比如 1,2,3... ,但是出于不希望被用户轻易看破某些规律从而了解到后台当中的某些信息,通常我们可以采用 md5算法 进行加密,将用户ID、用户名和时间戳作为算法输入的明文,把输出所得的密文作为 token ; 而 authcode 则是一串随机的字符串。

补充: MD5消息摘要算法(英语:MD5 Message-Digest Algorithm),是一种被广泛使用的密码散列函数,可以产生出一个128位(16字节)的散列值(hash value),用于确保信息传输完整一致。

参考:wiki-MD5

三、注册 SendCloud

首先自然是要介绍一下 SendCloud:

SendCloud 是国内首个触发邮件发送平台,旨在为广大开发者提供优质的第三方邮件发送服务,让客户不再为邮件拒信,垃圾误判等问题所困扰。

更多信息:SendCloud 官方网站

3.1 注册 SendCloud 获取 API_USER 和 API_KEY

由于 SendCloud 在调用 API 的过程中需要传入个人的 API_USER 和 API_KEY 作为参数,因此我们需要先进行注册

进入 SendCloud 官网进行注册。

此处输入图片的描述

注册之后登录 SendCloud 的“邮件中心”,系统已经自动生成了一个触发类型的 API_USER ,而 API_KEY 则是通过邮件的方式发送给你。

此处输入图片的描述

此处输入图片的描述

至此我们就顺利获得了 API_USER 和 API_KEY 。

3.2 配置邮件

SendCloud 提供了两种邮件类型:

  • 触发邮件:在某种场景下,由事件触发的邮件发送。比如:注册激活,密码找回,站内通知,信息确认,账单寄送等。
  • 批量邮件:商家对会员发送的通知邮件。比如:新功能上线,打折优惠等。

本次课程要做的就是实现注册激活,因此我们将采用触发类型的邮件。

另外 SendCloud 还提供了五种邮件模板,而且都支持自定义。

此处输入图片的描述

并且 SendCloud 支持在邮件中使用 「变量」 。变量的格式为: %variable% 。这些变量是可以在调用 API 的时候作为参数动态传入进行替换,因此如果你希望邮件的某一个字段能根据具体的请求再填入具体的值得时候(如:用户名,链接,消费额等)就可以将该字段采用变量进行表示。接下来我以 「SendCloud测试模板-普通发送」 为例进行修改,同学们也可以自由发挥定义出自己想要的邮件模板。

此处输入图片的描述

进入 「SendCloud测试模板-普通发送」 的修改界面,然后:

  1. 如上图1,将邮件类型修改为 「触发类型」 , 因为 「触发类型」 的 API_USER 只能请求发送 「触发类型」 的邮件模板
  2. 如上图2,点击 「源码」 按钮
  3. 如上图3,修改模板的文本内容,并且添加 %name% 作为用户名变量。
  4. 如上图4,将超链接标签处修改为 <a href="%url%">%url%</a> ,添加了变量 %url% ,用于展示邮箱验证的链接。
  5. 提交审核
  6. 审核通过之后便可使用

修改后的预览效果:

此处输入图片的描述

在实际发送的邮件中 %name% 和 %url% 变量都会被替换。

更多关于 SendCloud 的服务机制、变量替换以及邮件模板的等基本概念信息请参考:开发者文档

3.3 邮件 API

SendCloud 为用户提供了 SMTP 和 WEBAPI 两种调用接口的方式。

  • 使用 SMTP 协议传输数据到 SendCloud 的邮件服务器
    • 用户可以编写程序连接邮件服务器,发送邮件
    • 用户可以配置客户端连接邮件服务器,只需修改用户名,密码和 SMTP 配置即可
  • WEBAPI 是使用 HTTP 接入 SendCloud 服务的一种方式。用户可以利用 SendCloud 提供的 HTTP 接口,调用 SendCloud 的服务。
    • 普通发送方式
    • 模板发送方式

本次课程我们将采用 WEBAPI 中的模板发送方式。

模板发送的请求信息如下:

URL:

http://www.sendcloud.net/webapi/mail.send_template.json

HTTP请求方式:

POST

部分参数说明:

此处输入图片的描述

SendCloud 的介绍到此结束,接下来我们将开始配置数据库。

更多信息请参考:邮件 API

四、配置数据库 MySQL

在开始编写后台程序之前,我们需要建立相应数据库来存储用户信息。

启动 mysql 数据库服务。

$ sudo service mysql start

此处输入图片的描述

以 root 账户登录 mysql 数据库。

$ mysql -u root

此处输入图片的描述

登录数据库之后,首先要创建新的数据库 'shiyanlou' :

mysql> create database shiyanlou;

此处输入图片的描述

之后链接新创建的数据库 'shiyanlou' :

mysql> use shiyanlou;

此处输入图片的描述

在 'shiyanlou' 数据库中创建一个 'user' 表,用于存储用户信息。

create table user(`id` int(10) not null primary key auto_increment,`name` varchar(20) not null,`email` varchar(50) not null unique,`token` char(50) null,`verification_status` boolean not null default '0',`authcode` varchar(50) null,`created_time` timestamp default current_timestamp);

此处输入图片的描述

然后执行以下命令,退出 mysql 。

mysql> quit;

五、编写后台程序

接下来我们要开始编写后台程序,后台程序负责的工作包括:

  1. 对浏览器的 GET 或者 POST 请求作出相应的相应
  2. 获取 POST 方法中的用户信息,生成相应的 token 和 authcode 并写入数据库中
  3. 构造邮箱验证链接,并调用 SendCloud 的 WEBAPI 进行发送邮件
  4. 获取验证链接中的 token 和 authcode 字段的值,查询数据库进行验证,如果正确,修改相应用户的 verificatin_status

5.1 创建项目列表

打开实验桌面,在 shiyanlou/Code/ 的目录下创建项目文件夹 email_verify ,之后本课程所有的项目文件都位于该目录之下。

此处输入图片的描述

之后在 shiyanlou/Code/email_verify 目录下创建以下文件,其中 .pyc 是 .py 文件搬移产生的二进制文件,不需要自行创建。

此处输入图片的描述

温馨提示:创建目录可以使用 mkdir 命令,创建文件可以使用 touch 命令。

待创建的目录及文件详解:

  • func/:放置自定义模块
    • assist.py:实现 MD5 加密和产生固定长度的随机验证码
    • db.py:数据库类定义文件,封装了所需使用的数据库操作。
    • __init__.py:使得该目录可以被当作模块导入,虽然 __init__.py 只是一个空文件。
    • process.py:执行发送邮件,验证邮件,操作数据库等功能。
  • register.py:基于 Flask 框架的简单后台,能响应 GET 与 POST 请求
  • templates/:网页渲染模板
    • display.html:展示结果界面
    • register.html:注册界面

5.2 安装 Flask, requests, hashlib 和 MySQLdb 模块

因为文章开头已经讲过了如何配置,所以这里不再赘述,如果还没有安装这些模块的同学可以返回文章开头参考 1.3 进行安装。

5.3 开始编写后台程序

5.3.1 编写基于 Flask 框架的网页后台

register.py 使用了 Flask 框架,但是涉及的并不深入,所以不懂得这个框架的同学也不用慌,参考下边的快速入门链接,花个十几分钟学会如何使用 Flask 框架写出 Hello World 便足够看懂以下代码。

补充:Flask是一个使用Python编写的轻量级Web应用框架。基于Werkzeug WSGI工具箱和Jinja2 模板引擎。

参考1:wiki-Flask
参考2:Flask 快速入门

register.py

# coding:utf-8from flask import *from func.process import write_data, send_email, verify_emailapp = Flask(__name__)@app.route("/register", methods=['POST', 'GET'])def do_register():    """    获取用户名和邮箱信息,写入数据库,并发送请求    :return:    """    if request.method == 'POST':        # 获取用户名和邮箱信息        name = request.form['name']        email = request.form['email']        # 将数据写入数据库        outcome = write_data(name, email)        if outcome is False:            return redirect(url_for('error'))        # 发送邮件        send_email(name, email)        return redirect(url_for('wait_verifyed'))    return render_template('register.html')@app.route("/error")def error():    msg = "Your email have already existed!"    return render_template('display.html', msg=msg)@app.route("/do_verificatin", methods=['GET'])def do_verification():    """    进行邮箱认证    :return:    """    # 获取 GET 请求的参数 token 与 authcode 的值    token = request.args.get('token')    authcode = request.args.get('authcode')    # 调用verify_email函数进行认证    if token is not None and authcode is not None and verify_email(token, authcode):        return redirect(url_for('success'))    else:        return redirect(url_for('fail'))@app.route("/success")def success():    msg = "success"    return render_template('display.html', msg=msg)@app.route("/fail")def fail():    msg = "Too late!Time out!"    return render_template('display.html', msg=msg)@app.route("/wait_verifyed")def wait_verifyed():    msg = "Our verification link has sent to your email, please check your E-mail !"    return render_template('display.html', msg=msg)if __name__ == "__main__":    # 在调试模式下启动本地开发服务器    app.run(debug=True)

5.3.2 编写process.py文件

process.py 文件的主要功能是:

  1. 将从后台获取的用户数据写入数据库中
  2. 调用 SendCloud 的 WEBAPI 接口发送邮件
  3. 从后台获取 token 和 authcode 查询数据库进行验证

补充:这里边使用到了 requests,datetime 模块,不熟悉这些模块使用方式的同学可以参考以下链接。

参考1:requests
参考2:datetime

process.py:

# coding:utf-8from func.db import DataBaseimport requests, json, datetime# 数据库参数host = 'localhost'user = 'root'pwd = ''base = 'shiyanlou'table = 'user'# 调用 SendCloud 的 WEBAPI 所需参数API_USER = '请输入你的 API_USER'API_KEY = '请输入你的 API_KEY'url = "http://www.sendcloud.net/webapi/mail.send_template.json"# 邮箱验证链接base_link = "http:127.0.0.1:5000/do_verificatin?"# 邮箱的有效认证时长为1天(s)one_day_in_second = 5184000def write_data(name, email):    """    将用户名和邮箱写入数据库    :param name: 用户名    :param email: 邮箱    :return:    """    database = DataBase(host, user, pwd, base)    database.set_table(table)    outcome = database.insert_record(name, email)    database.close()    return outcomedef send_email(name, email):    """    给指定邮箱发送验证信息    :param name:    :param email:    :return:    """    database = DataBase(host, user, pwd, base)    database.set_table(table)    # 使用 email 和 name 查询数据库    # 数据库查询返回的记录是一个 tuple    # 分别获取 name, email, token, authcode 信息    record = database.query_by_email(name, email)    database.close()    name = record[1]    email = record[2]    token = record[3]    authcode = record[5]    # 构造完整的邮箱认证链接    link = base_link + 'token=%s&authcode=%s' % (token, authcode)    # "to":指定目标邮箱    # "sub":指定替换变量    # 将模板中定义的变量 %name% 和 %url% 分别进行替换成真实值    sub_vars = {        'to': [email],        'sub': {            '%name%': [name],            '%url%': [link],        }    }    # 构造请求的内容    params = {        "api_user": API_USER,        "api_key": API_KEY,        "template_invoke_name": "test_template_send",        "substitution_vars": json.dumps(sub_vars),        "from": "postmaster@shiyanlou.sendcloud.org",        "fromname": "shiyanlou",        "subject": "Welcome to Shiyanlou",        "resp_email_id": "true",    }    # 使用 POST 方式发送请求至 SendCloud 的 WEBAPI    # url:指定了请求目标    # data:指定了请求内容    r = requests.post(url, data=params)    # 获取请求返回的状态    # 200 说明请求成功,否则失败    if r.status_code == 200:        return True    else:        return Falsedef verify_email(token, authcode):    """    验证邮箱    :param token: 用户唯一标识    :param authcode: 验证码    :return:    """    database = DataBase(host, user, pwd, base)    database.set_table(table)    record = database.query_by_token(token, authcode)    created_time = record[6]    # 计算时间差    # 如果时间差大于1天,则验证无效    # 否则更新数据库    d = (datetime.datetime.now() - created_time).total_seconds()    if d > one_day_in_second:        database.close()        return False    else:        database.update(token, authcode)        database.close()        return True

注意:请将 API_USER 和 API_KEY 替换成你自己的。

5.3.3 编写数据库类

db.py 主要定义了数据库类,封装了一些所需的数据库查询、更新、插入操作。

补充:这里涉及 MySQLdb 模块的使用,可以参考 MySQLdb 使用

db.py:

# coding:utf-8import MySQLdbimport datetimefrom func.assist import get_token, get_authcodeclass DataBase:    def __init__(self, hostname, user, pwd, base_name):        self.db = MySQLdb.connect(hostname, user, pwd, base_name)        self.cursor = self.db.cursor()        self.base = base_name        self.table = 'user'    def set_table(self, table):        self.table = table    def query_by_id(self, id):        """        通过 id 进行查询        :param id:        :return:        """        sql = "select * from %s where `id`=%s" % (self.table, id)        try:            self.cursor.execute(sql)            data = self.cursor.fetchone()            return data        except:            print("Error: unable to fetch data using 'id'=%s" % id)    def query_by_email(self, name, email):        """        通过 email 和 name 进行查询        :param name:        :param email:        :return:        """        sql = "select * from %s where name='%s' and email='%s'" % (self.table, name, email)        try:            self.cursor.execute(sql)            data = self.cursor.fetchone()            return data        except:            print("Error: unable to fetch data using 'email'=%s" % email)            return None    def query_by_token(self, token, authcode):        """        通过 token 和 authcode 进行查询        :param token:        :param authcode:        :return:        """        sql = "select * from %s where token='%s' and authcode='%s'" % (self.table, token, authcode)        try:            self.cursor.execute(sql)            data = self.cursor.fetchone()            return data        except:            print("Error: unable to fetch data using token")            return False    def insert_record(self, name, email):        """        插入用户数据        :param name:        :param email:        :return:        """        id = self.get_max_id()        if id is None:            id = 0        elif id == -1:            return        id = id + 1        # 获取当前时间        current_time = datetime.datetime.now()        # 利用 id, name, current_time 生成用户唯一标识的 token        token = get_token(id, name, current_time.strftime("%Y-%m-%d %H:%M:%S"))        # 获取验证码        authcode = get_authcode()        sql = "insert into %s(name, email, token, authcode, created_time) values('%s', '%s', '%s', '%s', '%s')" % \              (self.table, name, email, token, authcode, current_time)        try:            self.cursor.execute(sql)            self.db.commit()            return True        except:            print('Error: unable to insert data (name:%s, email:%s)' % (name, email))            self.db.rollback()            return False    def update(self, token, authcode):        status = 1        sql = "update %s set verification_status=%s where token='%s' and authcode='%s'" % \              (self.table, status, token, authcode)        try:            self.cursor.execute(sql)            self.db.commit()            print("commit!")        except:            self.db.rollback()    def get_max_id(self):        """        获取当前数据库记录中的最大 id        :return:        """        sql = "select max(id) from %s" % self.table        try:            self.cursor.execute(sql)            id = self.cursor.fetchone()            return id[0]        except:            print("Error: unable to get the last insert id")            return -1    def close(self):        """        关闭数据库        :return:        """        self.db.close()

5.3.4 编写 assist.py

assist.py 相对较简单,主要功能就两个:

  1. 利用 id, name, time 参数生成用户唯一标识 token
  2. 生成固定长度的随机字符串

assist.py:

# coding:utf-8import hashlib, random, stringdef get_token(id, name, time):    """    使用由 id, name, time 组成的明文生成相应密文    :param id:    :param name:    :param time:    :return:    """    data = "%s%s%s" % (id, name, time)    hash_md5 = hashlib.md5(data)    return hash_md5.hexdigest()def get_authcode(length=20):    """    生成长度为 length 的随机字符串作为验证码    :param length:    :return:    """    char_set = list(string.digits + string.ascii_letters)    random.shuffle(char_set)    return "".join(char_set[:length])

六、编写前端程序

前端涉及的主要是 HTML、JavaScript 和 CSS 的知识,既然本次实验是以学习 LeanCloud 的短信验证 API 为主,那么这些就不是我们要关心的重点。

这里提供源代码,感兴趣的同学不妨看看,而不想写前端的同学也可以直接复制使用。

register.html:

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>Login</title>    <script type="text/javascript">    var name;    var email;    function do_checking()    {        name = document.getElementById("name").value;        email = document.getElementById("email").value;        if(!name || !email)        {            alert("Please input the necessary information");        }    }    </script>    <style>        #name{            position:relative;            top:10px;            left:50px;            width:210px;        }        #email{            position:relative;            top:30px;            left:50px;            width:210px;        }        #submit{            position:relative;            top:50px;            left:50px;            width:210px;        }        #getCode:hover,#submit:hover{            cursor:pointer;            background-color:#666;            color:#FFF;        }        input,button{            border:1px solid #999;            height:40px;            color:#666;        }        .h1{            position:relative;            top:10px;            left:130px;            color:#666;        }        .box{            top:20%;            left:33%;            height:300px;            width:300px;            border:dashed 2px #666;            position:absolute;        }        .warning{            position:absolute;            left:452px;            top:522px;        }    </style></head><body><div class="box">    <h1 class="h1">Lab</h1>    <form action="#" method="post">    <div>        <input type="text" id="name" name="name" placeholder="input your name">    </div>    <div>        <input type="text" id="email" name="email" placeholder="input your E-mail">    </div>    <div>        <button type="submit" id="submit" onclick="do_checking()">Submit</button>    </div>    </form></div></body></html>

display.html

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>Title</title></head><body>{% if msg %}    <h1>{{ msg }}</h1>{% else %}{% endif %}</body></html>

七、运行

在整个实验完成后,进入 shiyanlou/Codes/email_verify 文件夹中,运行命令:

$ python reigster.py

此处输入图片的描述

然后打开浏览器访问 http://127.0.0.1:5000/register 进行测试。

此处输入图片的描述

八、总结

本次课程我们学习了如何使用 SendCloud 提供的 WEBAPI 给指定用户发送邮件实现了邮箱认证的功能。

此外 SendCloud 的邮件 API 能做的事情可不只有邮箱认证这么点,比如还可以用它来定期发送消费记录、发送日程提醒等等,在学过这门课之后,希望大家能开动脑洞,利用这些 API 去做一些更加有趣的东西。

温馨提示:
同学们要记得在实验之后及时完成实验报告并且上传,有不懂的问题欢迎留言交流和讨论。

你还可以通过以下命令获取实验源码:

$ wget http://labfile.oss.aliyuncs.com/courses/622/email_verify.tar

温馨提示:
下载之后记得将 func/prcess.py 文件中 API_USER 和 API_KEY ,替换成你自己申请所得的相应值,另外还要参见步骤三创建数据库,否则程序将无法正常完成验证。

参考资料

  • Python requests 快速入门 - http://docs.python-requests.org/zh_CN/latest/user/quickstart.html
  • Flask 快速入门 - http://docs.jinkan.org/docs/flask/quickstart.html
  • SendCloud 官网 - http://sendcloud.sohu.com/email.html
  • Python 操作 MySQLdb - http://www.runoob.com/python/python-mysql.html
  • Python 模块之 Datetime 官方文档- https://docs.python.org/2/library/datetime.html
0 0
原创粉丝点击