学习随笔 itsdangerous生产确认邮件

使用itsdangerous生产确认令牌

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

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

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

    # 生成exporation为有效期十分钟的令牌
    def generate_confirmation_token(self, expiration=600):
        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

    # @property装饰器就是负责把一个方法变成属性调用的,把一个getter方法变成属性,只需要加上@property就可以了,
    # 此时,@property本身又创建了另一个装饰器@password.setter,负责把一个setter方法变成属性赋值,于是,我们就拥有一个可控的属性操作
    @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.name

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

发送确认令牌邮件

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

# 登陆
@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.remember_me.data)   # 在用户会话中把用户标记为已登陆
            # 用户访问未授权的URL时会显示登陆表单,Flask-Login会把原地址保存在查询字符串的next参数中,这个参数可以从request.args字典中读取
            return redirect(request.args.get(next) or url_for('main.index'))
        flash('Invaliid username or password.')
    return render_template('auth/login.html', form=form)

# 注册时发送确认邮件
@auth.route('/register', methods=['GET', 'POST'])
def register():
    form = RegistrationForm()
    if form.validate_on_submit():
        user = User(email=form.email.data,
                    username=form.username.data,
                    password=form.password.data)
        db.session.add(user)
        token = user.generate_confirmation_token()
        send_email(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)

# 重发邮件
@auth.route('/confirm')
@login_required
def resend_confirmation():
    token = current_user.generate_confirmation_token()
    send_email(current_user.email, 'Confirm Your Accout', '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'))

# 验证用户
@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'))

# 已注册未验证用户
@auth.route('/unconfirmed')
def unconfirmed():
    if current_user.is_anonymous or current_user.confirmed:
        return redirect(url_for('amin.index'))
    return render_template('auth/unconfirmed.html')

# 拦截程序中过滤未确认的账户
@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('/logout')
@login_required
def logout():
    logout_user()
    flash('You have been logged out.')
    return redirect(url_for('main.index'))

发送邮件脚本

from threading import Thread
from flask import current_app, render_template
from flask_mail import Message
from . import mail

# 异步发送邮件
def send_async_email(app, msg):
    with app.app_context():
        mail.send(msg)

def send_email(to, subject, template, **kwargs):
    app = current_app._get_current_object()
    msg = Message(app.config['FLASKY_MAIL_SUBJECT_PREFIX'] + ' ' + subject,
                  sender=app.config['FLASKY_MAIL_SENDER'], recipients=[to])
    msg.body = render_template(template + '.txt', **kwargs)    # 邮件正文
    msg.html = render_template(template + '.html', **kwargs)   # 邮件格式
    thr = Thread(target=send_async_email, args=[app, msg])      # 创建进程
    thr.start()
    return thr

遇到的问题:

1.导入配置文件时config.py时,如果直接用这样的形式导入数据,数据是元组,而非字符串或整型

class Config:
    MAIL_SERVER = 'smtp.qq.com'
    MAIL_PORT = 587
   
    @staticmethod
    def init_app(app):
        pass
2. before_request钩子只能应用到属于蓝本的请求上。而想在蓝本中使用针对全程全局请求,必须使用 before_app_request装饰器

猜你喜欢

转载自blog.csdn.net/hongchen37/article/details/80302908
今日推荐