__________________________________________________前言________________________________________________________
我们先来看上节的注册视图函数:
@auth.route('/register', methods=['GET', 'POST']) def register(): form = RegisterForm() #创建表单 if form.validate_on_submit(): #填写的表单数据通过验证函数 u = User(email=form.email.data, username=form.username.data, password=form.password.data) #创建用户 db.session.commit(u) #把用户添加到会话 flash('You can now login.') #提示用户已经可以登录 return redirect(url_for('auth.login')) #重定向 return render_template('auth/register.html', form=form)
用户填写完注册表单点击提交按钮后, 会通过validate_on_submit验证, 然后创建该用户, 把该用户提交到会话。
但此处存在一个问题, 我们不知道用户输入的email是不是有效的email, 我们到底能不能通过该email联系上用户, 所以我们要添加一个发送确认邮件的功能, 思路如下:
1.用户注册以后, 我们先提交会话, 因为id是数据库分配的, 只有提交会话以后用户才有id;
2.向用户发送一封确认邮件, 该邮件的主要内容是一含有用户id的确认链接,该链接映射确认函数;
3.用户打开邮箱点击确认链接, 视图函数处理用户请求, 先要求用户登录——login_required,然后对照登录用户的id是否和链接中的id相同, 如果相同, 则验证成功, 把用户的confirm字段改为True。
————————————————————————————————————————————————————
以上就是我们的思路, 但是考虑还不够周到:
用户id比较容易猜测, 如果恶意用户知道了链接格式, 便可轻松确认任何用户, 所以我们要对用户id进行加密, 借助itsdangerous包把用户id加密成token字符串;
在发送邮件之前生成token字符串, 点击链接之后验证token字符串就好了。
下面对我们的程序进行修改:
——————————————————————分界线——————————————————————————
一. 修改|-app/models.py
token是由用户id生成的, 验证的时候用户id是由token解密得到的, 所以生成token和验证token的函数应该放到User表中:
from . import db from itsdangerous import TimedJSONWebSignatureSerializer as Serializer from flask import current_app class User(db.Model): #... confirm = db.Column(db.Boolean, default=False) #添加验证字段 def generate_token(self, expiration=3600): s = Serializer(current_app.config['SECRET_KEY'], expires_in=expiration) #生成签名 return s.dumps({'confirm': self.id}) #返回加密后的token字符串 def confirm_token(self, token): s = Serializer(current_app.config['SECRET_KEY']) try: data = s.loads(token) #解密token, 得到id字典 except: return False if data.get('confirm') != self.id: #如果token中的id不等于当前用户id, 验证失败 return False self.confirm = True #修改该用户的confirm字段 db.session.add(user) #把用户添加到会话里 return True
*我们在User表中新加了confirm属性, 需要我们重新生成迁移脚本, 并把迁移应用到数据库中, 我们在前面的章节已经有介绍怎么做, 此处不再赘述。
二. 修改|-app/-auth/views.py
我们需要在注册视图里添加发送邮件的功能, 然后新增加一个确认路由:
from . import auth from .forms import RegisterForm from ..models import User from .. import db from ..email import send_mail from flask import redirect, url_for, flash, render_template from flask_login import login_required @auth.route('/register', methods=['GET', 'POST']) def register(): form = RegisterForm() #注册表单 if form.validate_on_submit(): #用户提交的表单数据通过验证函数时为True user = User(email=form.email.data, username=form.username.data, password=form.password.data) db.session.add(user) db.session.commit() #把会话提交到数据库中后用户才有id token = user.generate_token() #生成token字符串 send_mail(user.email, 'Confirm your account', 'auth/email/confirm', user=user, token=token) #向用户发确认邮件 flash('a confirm email has been sent to your email.') return redirect(urll_for('main.index')) return render_template('auth/register', form=form) @auth.route('/confirm/<token>') @login_required def confirm(token): if current_user.confirm: return redirect(url_for('main.index')) #防止用户重复点击验证链接 if current_user.confirm_token(token): #无论验证成功与否, 都先提示用户然后返回主页 flash('You have confirmed your account successfully.') else: flash('Confirm failed.') return redirect(url_for('main.index'))
三. 修改|-app/templates/auth/email/confirm.txt
Dear {{ user.username }} Please click the link below to confirm your account: {{ url_for('auth.confirm', token=token, _external=True) }}
confirm.html与.txt文件类似, 不再赘述。
*url_for生成的是相对地址, 只能用在程序网页的上下文中, 在邮箱中需要绝对地址, 所以把_external参数设置为True。
四. 效果演示
1.填写注册信息
2.我们点击提交以后, 发送post请求到register视图函数, 创建用户并提交到数据库:
3.发送邮件, 显示提示信息, 并重定向到主页:
4.打开邮箱, 发现有一封新邮件:
5.点击邮件的链接, 因为confirm视图函数有@login_required修饰器, 没有登陆也就没有得到访问该函数的授权,所以会重定向到登录页面:
6.填写完登录信息后, 点击提交按钮,
这时由auth.login视图函数处理post请求, login函数在验证完用户密码后返回重定向, 代码是——return redirect(request.args.get('next') or url_for('main.index')), 第一个参数的作用是存储上次用户访问的未授权的url, 也就是被login_required阻止访问的验证函数的url, 重新访问confirm函数, 通过验证, 数据库中用户的confirm字段被修改为1:
7.程序提示认证成功并返回主页:
*上面图片的文字介绍都是按照代码的执行顺序来解释的。