• Flask-Login: 管理已登录用户的用户会话。
• Werkzeug: 计算密码散列值并进行核对。
• itsdangerous: 生成并核对加密安全令牌。
1. 创建认证蓝本:app/auth/__init__.py
from flask import Blueprint
auth = Blueprint('auth', __name__)
from . import views
2. 注册认证蓝本到create_app()工厂:app/__init__.py:
def create_app(config_name):
# ...
from .auth import auth as auth_blueprint
app.register_blueprint(auth_blueprint, url_prefix='/auth')
return app
3. 安装flask-login插件
pip install flask-login
4. 创建User模型保存用户登录信息:app/models.py
from flask_login import UserMixin
from . import db
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))
role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
def __repr__(self):
return '' % self.username
5. 在User模型中实现密码散列:app/models.py
from werkzeug.security import generate_password_hash, check_password_hash
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
# ...
class User(UserMixin, db.Model):
# ...
@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)
说明:要保证数据库中用户密码的安全,关键在于不能存储密码本身,而要存储密码的散列 值。计算密码散列值的函数接收密码作为输入,使用一种或多种加密算法转换密码,最终 得到一个和原始密码没有关系的字符序列。核对密码时,密码散列值可代替原始密码,因 为计算散列值的函数是可复现的:只要输入一样,结果就一样。
6. 初始化Flask-Login:app/__init__.py
from flask_login import LoginManager
login_manager = LoginManager()
login_manager.session_protection = 'strong'
login_manager.login_view = 'auth.login'
def create_app(config_name):
# ...
login_manager.init_app(app)
# ...
说明:LoginManager 对象的 session_protection 属性可以设为 None、'basic' 或 'strong',以提 供不同的安全等级防止用户会话遭篡改。设为 'strong' 时,Flask-Login 会记录客户端 IP 地址和浏览器的用户代理信息,如果发现异动就登出用户。login_view 属性设置登录页面 的端点。回忆一下,登录路由在蓝本中定义,因此要在前面加上蓝本的名字。
7. 设置用户回调函数:app/model.py
from . import login_manager
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
说明:Flask-Login 要求程序实现一个回调函数,使用指定的标识符加载用户。加载用户的回调函数接收以 Unicode 字符串形式表示的用户标识符。如果能找到用户,这 个函数必须返回用户对象;否则应该返回 None。
8. 通过Flask-Login提供的login_required装饰器来增加路由保护, 例如:
from flask_login import login_required
@app.route('/secret')
@login_required
def secret():
return 'Only authenticated users are allowed!'
说明:如果未认证用户访问这个路由,Flask-Login会将这个请求发往登录页面。
9. 添加登录表单:app/auth/forms.py
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField, SubmitField
from wtforms.validators import DataRequired, Length, Email, Regexp, EqualTo
from wtforms import ValidationError
class LoginForm(FlaskForm):
email = StringField(u'邮箱', validators=[DataRequired(), Length(1, 64), Email()])
password = PasswordField(u'密码', validators=[DataRequired()])
remember_me = BooleanField(u'记住我')
submit = SubmitField(u'登录')
10. 在视图中添加登录/登出功能:app/templates/base.html
11. 实现登录功能:app/auth/views.py
from flask import render_template, redirect, request, url_for, flash
from flask_login import login_user
from . import auth
from ..models import User
from .forms import LoginForm
@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)
return redirect(request.args.get('next') or url_for('main.index'))
flash('Invalid username or password.')
return _render('login', locals())
12. 渲染登录页面: app/templates/auth/login.html
{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block title %}登录{% endblock %}
{% block page_content %}
Login
{{ wtf.quick_form(form) }}
{% endblock %}
13. 实现登出功能:app/auth/views.html
@auth.route('/logout')
@login_required
def logout():
logout_user()
flash('You have been logged out.')
return redirect(url_for('main.index'))