? 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 '<User %r>' % 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
<ul class="nav navbar-nav navbar-right">
{% if current_user.is_authenticated() %}
<li><a href="{{ url_for('auth.logout') }}">Sign Out</a></li>
{% else %}
<li><a href="{{ url_for('auth.login') }}">Sign In</a></li>
{% endif %}
</ul>
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 %}
<div class="page-header">
<h1>Login</h1>
</div>
<div class="col-md-4">
{{ wtf.quick_form(form) }}
</div>
{% 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'))