1. 程式人生 > >使用者認證(二)【使用Flask-Login認證使用者】

使用者認證(二)【使用Flask-Login認證使用者】

Flask-Login擴充套件,記住認證狀態,管理使用者認證系統中的認證狀態
pip install flask-login

準備用於登入的使用者模型

這裡寫圖片描述
這裡寫圖片描述
這裡寫圖片描述
這裡寫圖片描述
這裡寫圖片描述
flasky/app/models.py

from werkzeug.security import generate_password_hash, check_password_hash
from flask_login import UserMixin
###從flask_login匯入UserMixin類
###USerMixin類包含的以上四種方法的預設實現。
from . import db, login_manager
###從程式的工廠函式引入login_manager例項
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',lazy='dynamic') def __repr__(self): return '<Role %r>' % self.name class User(UserMixin, db.Model)
:
###User繼承UserMixin和db.Model類的功能屬性 __tablename__ = 'users' id = db.Column(db.Integer, primary_key=True) email = db.Column(db.String(64), unique=True, index=True) ###加入email屬性,用來儲存使用者的email 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)) @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 @login_manager.user_loader ###載入使用者的回撥函式接收以Unicode字串形式表示的使用者標示符 ###如果能找到使用者,這個函式必須返回使用者物件,否則返回None。 def load_user(user_id): return User.query.get(int(user_id))

這裡寫圖片描述
flasky/app/_init_.py

from flask import Flask
from flask_bootstrap import Bootstrap
from flask_mail import Mail
from flask_moment import Moment
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager
from config import config

bootstrap = Bootstrap()
mail = Mail()
moment = Moment()
db = SQLAlchemy()

login_manager = LoginManager()
login_manager.session_protection = 'strong'
login_manager.login_view = 'auth.login'


def create_app(config_name):
    app = Flask(__name__)
    app.config.from_object(config[config_name])
    config[config_name].init_app(app)

    bootstrap.init_app(app)
    mail.init_app(app)
    moment.init_app(app)
    db.init_app(app)
    login_manager.init_app(app)

    from .main import main as main_blueprint
    app.register_blueprint(main_blueprint)

    from .auth import auth as auth_blueprint
    app.register_blueprint(auth_blueprint, url_prefix='/auth')

    return app

保護路由

這裡寫圖片描述

新增登入表單

這裡寫圖片描述
這裡寫圖片描述
flasky/app/auth/forms.py登入表單
這裡寫圖片描述

from flask_wtf import Form
###從Flask-WTF擴充套件匯入Form基類
from wtforms import StringField, PasswordField, BooleanField, SubmitField
###從WTForms包中匯入欄位類
from wtforms.validators import Required, Length, Email
###從WTForms匯入驗證函式

class LoginForm(Form):
    email = StringField('Email', validators=[Required(), Length(1, 64), Email()])
    ###StringField建構函式中的可選引數validators指定一個有驗證函式組成的列表,在接受使用者提交的資料之前驗證資料。
    ###電子郵件欄位用到了WTForms提供的Length()和Email()驗證函式。
    password = PasswordField('Password', validators=[Required()]) 
    ###PasswordField類表示屬性為type="password"的<input>元素。
    remember_me = BooleanField('Keep me logged in')
    ###BooleanField類表示複選框。
    submit = SubmitField('Log In')

這裡寫圖片描述
這裡寫圖片描述
flasky/app/templates/base.html

{% extends "bootstrap/base.html" %}{% block title %}Flasky{% endblock %}{% block head %}{{ super() }}
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}" type="image/x-icon">
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}" type="image/x-icon">
{% endblock %}{% block navbar %}
<div class="navbar navbar-inverse" role="navigation">
    <div class="container">
        <div class="navbar-header">
            <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                <span class="sr-only">Toggle navigation</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="{{ url_for('main.index') }}">Flasky</a>
        </div>
        <div class="navbar-collapse collapse">
            <ul class="nav navbar-nav">
                <li><a href="{{ url_for('main.index') }}">Home</a></li>
            </ul>
            ########################################
            ###判斷條件中的變數current_user由Flask-Login定義,且在檢視函式和模板中自動呼叫。
            <ul class="nav navbar-nav navbar-right">
                {% if current_user.is_authenticated %}
                <li><a href="{{ url_for('auth.logout') }}">Log Out</a></li>
                {% else %}
                <li><a href="{{ url_for('auth.login') }}">Log In</a></li>
                {% endif %}
            </ul> 
            ########################################
        </div>
    </div>
</div>
{% endblock %}{% block content %}
<div class="container">
    {% for message in get_flashed_messages() %}
    <div class="alert alert-warning">
        <button type="button" class="close" data-dismiss="alert">&times;</button>
        {{ message }}
    </div>
    {% endfor %}{% block page_content %}{% endblock %}
</div>
{% endblock %}{% block scripts %}{{ super() }}{{ moment.include_moment() }}{% endblock %}

判斷條件中的變數current_user由Flask-Login定義,且在檢視函式和模板中自動呼叫。
這裡寫圖片描述
圖中程式碼中Sign Out和Sign in是錯的,程式碼應該是Log Out和Log In才能顯示網頁中右上角的效果。

登入使用者

登入路由【檢視函式login()】

這裡寫圖片描述
這裡寫圖片描述
這裡寫圖片描述

登出路由【檢視函式logout()】

這裡寫圖片描述

flasky/app/auth/views.py

from flask import render_template, redirect, request, url_for, flash
from flask_login import login_user, logout_user, login_required 
###從Flask_login匯入login_user, logout_user, login_required 函式
from . import auth 
###從本級目錄中匯入auth藍本
from ..models import User 
###從上級目錄中的models.py匯入User模型
from .forms import LoginForm
從本級目錄中的forms.py中匯入LoginForm類

@auth.route('/login', methods=['GET', 'POST'])
###當請求為GET時,直接渲染模板,當請求是POST提交時,驗證表格資料,然後嘗試登入使用者。
def login():
    form = LoginForm()
    if form.validate_on_submit():###表格中填入了資料,執行下面操作
        user = User.query.filter_by(email=form.email.data).first() 
        ###檢視函式使用表單中填寫的email載入使用者
        if user is not None and user.verify_password(form.password.data):
        ###如果user不是空的,而且驗證表格中的密碼正確,執行下面的語句,呼叫Flask_Login中的login_user()函式,在使用者會話中把使用者標記為登入。
        ###否則直接執行flash訊息和跳轉到新表格中。
            login_user(user, form.remember_me.data)
            ###login_user函式的引數是要登入的使用者,以及可選的‘記住我’布林值。
            return redirect(request.args.get('next') or url_for('main.index'))
            ###使用者訪問未授權的URL時會顯示登入表單,Flask-Login會把原地址儲存在查詢字串的next引數中,這個引數可從request.args字典中讀取。如果查詢字串中沒有next引數,則重定向到首頁。
        flash('Invalid username or password.')
    return render_template('auth/login.html', form=form)


@auth.route('/logout') 
###退出路由
@login_required 
###使用者要求已經登入
def logout():
    logout_user()
    ###登出使用者,這個檢視函式呼叫logout_user()函式,刪除並重設使用者會話。
    flash('You have been logged out.')
    ###顯示flash訊息
    return redirect(url_for('main.index'))
    ###重定向到首頁

login_user()函式的原型:flask.ext.login.login_user(user, remember=False, force=False, fresh=True)

登入表單時的模板

這裡寫圖片描述
這裡寫圖片描述

flasky/app/templates/auth/login.html

{% 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) }}
</div>
###加入渲染的表單
################################################
{% endblock %}

測試登入

這裡寫圖片描述
這裡寫圖片描述
這裡寫圖片描述
flasky/app/templates/index.html

{% extends "base.html" %}{% block title %}Flasky{% endblock %}{% block page_content %}
#####################################################
<div class="page-header">
    <h1>Hello, {% if current_user.is_authenticated %}{{ current_user.username }}{% else %}Stranger{% endif %}!</h1>
</div>
#####################################################
###用current._user.is_authenticated測試是否是登入的使用者首頁
{% endblock %}

此外,還有以下改動:
把之前main/views.py的各種複雜的儲存使用者會話訊息name,判斷是否是第一次輸入,以及傳送郵件等等去掉,簡化成直接返回index.html模板。
flasky/app/main/views.py

from flask import render_template
from . import main


@main.route('/')
def index():
    return render_template('index.html')

flasky/migrations/versions/456a945560f6_login_support.py
資料庫遷移指令碼版本

"""login support
Revision ID: 456a945560f6
Revises: 38c4e85512a9
Create Date: 2013-12-29 00:18:35.795259
"""

# revision identifiers, used by Alembic.
revision = '456a945560f6'
down_revision = '38c4e85512a9'

from alembic import op
import sqlalchemy as sa


def upgrade():
    ### commands auto generated by Alembic - please adjust! ###
    op.add_column('users', sa.Column('email', sa.String(length=64), nullable=True))
    op.add_column('users', sa.Column('password_hash', sa.String(length=128), nullable=True))
    op.create_index('ix_users_email', 'users', ['email'], unique=True)
    ### end Alembic commands ###


def downgrade():
    ### commands auto generated by Alembic - please adjust! ###
    op.drop_index('ix_users_email', 'users')
    op.drop_column('users', 'password_hash')
    op.drop_column('users', 'email')
    ### end Alembic commands ###

flasky/requirements.txt
所有依賴包的檔案

alembic==0.8.8
blinker==1.4
click==6.6
dominate==2.2.1
Flask==0.11.1
Flask-Bootstrap==3.3.7.0
Flask-Mail==0.9.1
Flask-Migrate==2.0.0
Flask-Moment==0.5.1
Flask-Script==2.0.5
Flask-SQLAlchemy==2.1
Flask-WTF==0.12
itsdangerous==0.24
Jinja2==2.8
Mako==1.0.4
MarkupSafe==0.23
python-editor==1.0.1
SQLAlchemy==1.0.15
visitor==0.1.3
Werkzeug==0.11.11
WTForms==2.1
Flask-Login==0.3.1 
###添加了flask-login包