使用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): pass2. before_request钩子只能应用到属于蓝本的请求上。而想在蓝本中使用针对全程全局请求,必须使用 before_app_request装饰器