十一、注册新用户

一、添加用户注册表单html

app/auth/forms.py   用户注册表单正则表达式

表单使用WTForms提供的Regexp验证函数,确保username字段只包含字母、数字、下划线和点号,这个验证函数中正则表达式后面的两个参数分别是正则表达式的旗标和验证失败时显示的错误消息shell

 
 
from flask_wtf import Form
from wtforms import StringField, PasswordField, BooleanField, SubmitField, validators, ValidationError
from wtforms.validators import Length, Email, Required, Regexp, EqualTo
from ..models import User

class
RegistrationFrom(Form): email = StringField('Email', validators=[Required(), Length(1, 64), Email()]) username = StringField('Username', validators=[Required(), Length(1, 64), Regexp('^[A-Za-z][A-Za-z0-9_.]*$', 0, 'Usernames, dots or underscores')]) password = PasswordField('Password', validators=[Required(), EqualTo('password2', message='Passwords must match.')]) password2 = PasswordField('Confirm password', validators=[Required()]) submit = SubmitField('Register') def validate_mail(selfself, field): if User.query.filter_by(email=field.data).first(): raise ValidationError('Eamil already registered') def validate_username(self, field): if User.query.filter_by(username=field.data).frist(): raise ValidationError('Username already in use.')

 

二、显示登陆表单数据库

app/templates/auth/register.htmlflask

{% extends "base.htmml" %}
{% import "bootstrap/wtf.html" as wtf %}

{% block title %}Flasky - Register {% endblock %}

{% block page_content %}
<div class="page-header">
    <h1>Register</h1>
</div>
<div class="col-md-4">
    {{ wtf.quick_form(form) }}
</div>
{% endblock %}

 

三、连接到注册页面bootstrap

app/templates/auth/login.html安全

登陆页面要显示一个指向注册页面的连接,让没有帐户的用户能轻易找到注册页面session

{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}

{% block title %}Flasky - Login{% endblock %}

{% block page_content %}
<div class="page-header">
    <h1>Login</h1>
</div>
<div class="col-md-4">
    {{ wtf.quick_form(form) }}
    <p>New user? <a href="{{ url_for('auth.register') }}">Click here to register</a>.</p>
</div>
{% endblock %}

 

注册新用户app

一、用户注册路由函数

app/auth/views.py

提交注册表单,经过验证后,系统就使用用户填写的信息在数据库中添加一个用户

from flask import render_template, redirect, request, url_for, flash
from flask_login import login_user, login_required, logout_user
from . import auth
from ..models import User,db
from .forms import LoginForm,RegistrationFrom

#蓝本中的路由和视图函数
@auth.route('/login', methods=['GET', 'POST'])
def login():
    form = LoginForm()
    if form.validate_on_submit():
        user = User.query.filter_by(email=form.email.data).first()
        if user is not None and user.verify_password(form.password.data):
            login_user(user, form.remeber_me.data)
            return redirect(request.args.get('next') or url_for('main.index'))
        flash('Invalid username or password.')
    return render_template('auth/login.html', form=form)

#登出用户
@auth.route('/logout')
@login_required
def logout():
    logout_user()
    flash('You have been logged out.')
    return redirect(url_for('main.index'))


#用户注册路由
@auth.route('/register', methods=['GET', 'POST'])
def register():
    form = RegistrationFrom()
    if form.validate_on_submit():
        user = User(email=form.email.data, username=form.username.data, password=form.password.data)
        db.session.add(user)
        flash('You can now login.')
        return redirect(url_for('auth.login'))
    return render_template('auth/register.html', form=form)

 

确认帐户

为确认注册用户时提供的信息是否正确,要求能经过电子邮件与用户取得联系,为验证电子邮件会,经过邮件发出一份确认邮件,帐户确认中每每要求用户点击一个包含确认令牌的特殊URL连接

确认邮件最简单的确认连接是http://www.example.com/auth/confirm/<id>这种形式的URL,其中id是数据库分配给用户的数字id。用户点击这个连接后,处理这个路由的视图函数就将接收到的用户id做为参数进行确认,而后将用户状态更新为已确认

这种方式不安全,透露出了后台的信息,解决方法是把URL中的id换成将相同安全加密后的到的令牌

示例:在shell中使用itsdangerous包生成包含用户id的安全令牌

itsdangerous提供不少生成令牌的方式,其中,TimedJSONWebSignatureSerializer 类生成具备过时时间的 JSON Web 签名 (JSON Web SignaturesJWS )。这个类的构造函数接受的参数是有个密钥,在Flask程序中使用SECRET_KEY设置
dumps()方法为指定的数据生成一个加密签名,而后再对数据和签名进行序列化,生成令牌字符串。expires_in参数设置令牌过时时间,单位为秒

loads()方法能够解码令牌,其惟一的参数是令牌支付串,这个方法会效验签名和过时时间,若是经过,返回原始数据,若是不正确,抛出异常

 

 

 

一、将生成和效验令牌的功能添加到User中

app/models.py    确认用户帐户

from . import db
from werkzeug.security import generate_password_hash, check_password_hash
from flask_login import UserMixin
from . import login_manager
from flask import current_app
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer

#定义数据库模型
class Role(db.Model):
    __tablename__ = 'roles'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64), unique=True)
    users = db.relationship('User', backref='role')

    def __repr__(self):
        return '<Role %r>' %self.name


#加载用户的回调函数
@login_manager.user_loader
def load_user(user_id):
    return User.query.get(int(user_id))


class User(UserMixin, db.Model):
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(64), unique=True, index=True)
    role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
    password_hash = db.Column(db.String(128))
    email = db.Column(db.String(64), unique=True, index=True)
    #确认用户帐户
    confirmed = db.Column(db.Boolean, default=False)

    def generate_confirmation_token(self, expiration=3600):
        s = Serializer(current_app.config['SECRET_KEY'], expiration)
        return s.dumps({'confirm': self.id})

    def confirm(self, token):
        s = Serializer(current_app.config['SECRET_KEY'])
        try:
            data = s.loads(token)
        except:
            return False
        if data.get('confirm') != self.id:
            return False
        self.confirmed = True
        db.session.add(self)
        return True

    #在User模型中加入密码散列
    @property
    def password(self):
        raise AttributeError('password is not a readable attribute')

    @password.setter
    def password(self, password):
        self.password_hash = generate_password_hash(password)

    def verify_password(self, password):
        return check_password_hash(self.password_hash, password)

    def __repr__(self):
        return '<User %r>' %self.username

 

二、发送确认邮件

当前/register路由把新用户添加到数据库中后,会重定向到/index。在重定向以前,这个路由须要发送确认邮件

app/auth/views.py   能发送确认邮件的注册路由

经过配置,程序已经能够在请求末尾自动化提交数据库变化,这里要添加db.session.commit()调用。问题在于,提交数据库以后才能赋予新用户id值,而确认令牌须要用到id,因此不能延后提交

一个邮件须要两个模板,分别渲染纯文本文件和富文本文件

from ..email import send_email

#用户注册路由
@auth.route('/register', methods=['GET', 'POST'])
def register():
    form = RegistrationFrom()
    if form.validate_on_submit():
        user = User(email=form.email.data, username=form.username.data, password=form.password.data)
        db.session.add(user)
        db.session.commit()
        token = user.generate_confirmation_token()
        send_mail(user.email, 'Confirm Your Accout', 'auth/email/confirm', user=user, token=token)
        flash('A confirmation email has been sent to you by email.')
        return redirect(url_for('main.index'))
    return render_template('auth/register.html', form=form)

 

三、确认邮件的纯文本正文

app/templates/auth/email/confirm.txt

默认状况下,url_for()生成相对URL,例如 url_for('auth.confirm', token='abc') 返回的字符串是 '/auth/confirm/abc'。这显然不是可以在电子邮件中发送的正确 URL。相对 URL 在网页的上下文中能够正常使用,由于经过添加当前页面的主机名和端口号,浏
览器会将其转换成绝对 URL。 但经过电子邮件发送 URL 时,并无这种上下文。添加到url_for() 函数中的 _external=True 参数要求程序生成完整的 URL,其中包含协议(http://https://)、主机名和端口

Dear {{ user.username }},

Welcome to Flasky!

To confirm your accout please click on the following link:

{{ url_for('auth.confirm', token=token, _external=True) }}

Sincerely,

The Flasky Team

Note: replies to this email address are not monitored

 

四、确认用户的帐户

app/auth/views.py

Flask-Login提供的login_required修饰器会保护这个路由,所以,当用户点击确认邮件中的连接后,要先登陆,而后才能执行这个视图函数

函数中,先检查当前登陆用户是佛已经确认过,确认过,重定向到首页。

from flask_login import current_user

#确认用户的帐户
@auth.route('/confirm/<token>')
@login_required
def confirm(token):
    if current_user.confirmed:
        return redirect(url_for('main.index'))
    if current_user.confirm(token):
        flash('You have confirmed your account. Thanks!')
    else:
        flash('The confirmation link is invalid or has expired.')
    return redirect(url_for('main.index'))

 

五、使用钩子在before_app_request处理程序中过滤未确认的帐户

app/auth/views.py

同时知足如下3个条件时,before_app_request处理程序会拦截请求

(1) 用户已登陆(current_user.is_authenticated() 必须返回 True)。
(2) 用户的帐户还未确认。
(3) 请求的端点(使用 request.endpoint 获取)不在认证蓝本中。访问认证路由要获取权限,由于这些路由的做用是让用户确认帐户或执行其余帐户管理操做。
若是知足以上所有条件,则会被重定向到/auth/unconfirmed路由,显示一个确认帐户相关信息的页面

#过滤未确认用户
@auth.before_app_request
def before_request():
    if current_user.is_authenticated() and not current_user.confirmed and request.endpoint[:5] != 'auth.' and request.endpoint != 'static':
        return redirect(url_for('auth.unconfirmed'))

@auth.route('/unconfirmed')
def unconfirmed():
    if current_user.is_annoymous() or current_user.confirmed:
        return redirect(url_for('main.index'))
    return render_template('auth/unconfirmed.html')

 

六、从新发送帐户确认邮件

app/auth/views.py

显示给未确认用户的页面只渲染一个模板,其中有如何确认帐户的说明,此外还提供了一个连接, 用于请求发送新的确认邮件,以防以前的邮件丢失 

这个路由也有login_required保护,确认访问时程序知道请求再次发送邮件的是哪一个用户

#给未确认用户,显示页面,是否须要重发确认邮件
@auth.route('/confirm')
@login_required
def resend_confirmation():
    token = current_user.generate_confirmation_token()
    send_mail(current_user.email, 'Confirm Your Account', 'auth/email/confirm', user=current_user, token=token)
    flash('A new confirmation email has been sent to you by email.')
    return redirect(url_for('main.index'))
相关文章
相关标签/搜索