flask forum開發(fā)筆記(3)登錄注冊(上)

一個完整的認證系統(tǒng)并不是簡簡單單的字符串相等,還應當考慮到安全性問題,比如用戶的密碼總不能直接暴露在數(shù)據(jù)庫中吧,萬一數(shù)據(jù)庫被入侵,那么用戶的密碼就全都泄露了。所以我將使用<b>Flask-Login、Werkzeug、itsdangerous</b>三個包做一個完整的認證系統(tǒng)。
<h2>1.使用Werkzeug實現(xiàn)密碼散列</h2>
在models.py文件中創(chuàng)建一個User模型

#coding:utf-8

from . import db
from werkzeug.security import generate_password_hash, check_password_hash

class User(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(32), unique = True, index = True)
    password_hash = db.Column(db.String(128))

    @property
    def password(self):
        raise AttributeError('密碼不是可讀屬性')

    @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)

這個User模型用Werkzeug實現(xiàn)了密碼散列,Werkzeug中的security模塊能夠很方便地實現(xiàn)密碼散列值的計算。這個功能的實現(xiàn)需要兩個函數(shù),分別用在注冊用戶和驗證用戶階段。

generate_password_hash(password, method=pdkdf2:sha1, salt_length=8)

這個函數(shù)將輸入的原始密碼以字符串形式輸出密碼的散列值。

check_password_hash(hash, password)

這個函數(shù)從數(shù)據(jù)庫中取回密碼散列值和用戶輸入的密碼,若返回值為True則表明密碼正確。
至此密碼散列功能就已經(jīng)完成了,可在shell模式下進行測試。

<h2>2.使用Flask-Login認證用戶</h2>
首先要創(chuàng)建一個新的認證藍本auth,專門用來存放于用戶認證相關的路由(auth藍本要在create_app()工廠函數(shù)中附加到程序上,所以創(chuàng)建藍本后要記得在app/__init__.py中附加藍本)。

Flask-Login要求User模型必須實現(xiàn)幾個方法,可以用Flask-Login提供的UserMixin類默認實現(xiàn)這些方法。修改后的User模型如下

#coding:utf-8

from . import db
from werkzeug.security import generate_password_hash, check_password_hash
from . import login_manager
from flask.ext.login import UserMixin

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(32), unique = True, index = True)
    password_hash = db.Column(db.String(128))

    @property
    def password(self):
        raise AttributeError('密碼不是可讀屬性')

    @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)

修改完User模型之后在程序的工廠函數(shù)(app/__init__.py)中初始化Flask-Login

#coding:utf-8
from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy
from flask.ext.bootstrap import Bootstrap
from flask.ext.login import LoginManager
from config import config

db =  SQLAlchemy()
bootstrap = Bootstrap()
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)

    db.init_app(app)
    bootstrap.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

LoginManager對象的session_protection屬性有三種設置(None,basic,strong),可以提供不同的安全等級防止用戶會話被篡改,我用的是strong,會記錄客戶端IP地址和瀏覽器的用戶代理信息。

最后在models.py文件中寫一個回調(diào)函數(shù),使用指定的標識符加載用戶

#coding:utf-8

from . import db
from werkzeug.security import generate_password_hash, check_password_hash
from . import login_manager
from flask.ext.login import UserMixin

class User(UserMixin, db.Model):
    __tablename__ = 'users'
   ...

@login_manager.user_loader
def load_user(user_id):
    return User.query.get(int(user_id))

完成以上步驟之后在auth文件夾中的forms.py新建LoginForm表單

#coding:utf-8
from flask.ext.wtf import Form
from wtforms import StringField, SubmitField, PasswordField, BooleanField
from wtforms.validators import Required, Length
from wtforms import ValidationError
from ..models import User

class LoginForm(Form):
    email = StringField('Email', validators=[Required(), Length(1,64), Email()])
    password = PasswordField('Password', validators=[Required()])
    remember_me = BooleanField('remember me')
    submit = SubmitField('Log In')

在views.py中寫login路由和視圖函數(shù)

#coding:utf-8
import sys
reload(sys)
sys.setdefaultencoding('utf8')

from .. import db
from . import auth
from ..models import User
from .forms import LoginForm
from flask import render_template, redirect, request, url_for, flash
from flask.ext.login import login_user, logout_user, login_required

@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('錯誤的用戶名或密碼')
    return render_template('auth/login.html', form=form)


@auth.route('/logout')
@login_required
def logout():
    logout_user()
    flash('您已成功退出用戶')
    return redirect(url_for('main.index'))

這里有一點需要注意,如果flash消息是中文的話,需在開頭加上這三行代碼才能正常顯示,不然會提示編碼錯誤的bug。

import sys
reload(sys)
sys.setdefaultencoding('utf8')

至于login的html模板則用了wtf表單

{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}

{% block title %}DX Studio forum-Login{% endblock %}

{% block page_content %}
<div class="page-header">
    <h1><b>Login</b></h1>
</div>
<div class="col-md-4">
    {{ wtf.quick_form(form) }}
    <br>
    <p>Forgot your password? <a href="#">Click here to reset it</a>.</p>
    <p>New user? <a href="#">Click here to register</a>.</p>
</div>
{% endblock %}

最終顯示效果如下

9DDF1486-C9B3-4207-B022-ED66A7708BD8.png

其實可以發(fā)現(xiàn)現(xiàn)在的頁面和之前起始篇中的html樣子已經(jīng)發(fā)生了改變,因為我用if current_user.is_authenticated修改了base模板,有些東西應當在用戶登錄后才顯示,比如個人信息頁面,以及沒登錄前顯示login登錄后顯示logout等等。修改例子如下

{% if current_user.is_authenticated %}
<li>
  <a href="{{ url_for('auth.logout') }}">
    <i class="fa fa-fw fa-power-off text-success-lter"></i>
    <span translate="aside.nav.your_stuff.PROFILE">退出用戶</span>
  </a>
</li>
{% else %}
<li>
  <a href="{{ url_for('auth.login') }}">
    <i class="icon-user icon text-success-lter"></i>
    <span translate="aside.nav.your_stuff.PROFILE">用戶登錄</span>
  </a>
</li>
{% endif %}

這里也有一點需要注意,《flask web開發(fā)》上is_authenticated()那時還是一個方法,但現(xiàn)在新版本的Flask-Login中這已經(jīng)不是一個方法了,而是作為一個屬性來使用,需要去掉括號,否則會出現(xiàn)bug。

OK,這章先寫到這里,開頭提到的its dangerous包并沒有寫到,是因為我這章只寫了登錄,itsdangerous包用在注冊,關于注冊方面的筆記將會在下一章出現(xiàn)。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • 22年12月更新:個人網(wǎng)站關停,如果仍舊對舊教程有興趣參考 Github 的markdown內(nèi)容[https://...
    tangyefei閱讀 35,412評論 22 257
  • 第二部分 Blog例子 第八章 用戶驗證 大部分程序需要追蹤用戶身份。當用戶連接到程序,通過一系列步驟使自己的身份...
    易木成華閱讀 1,412評論 0 4
  • 大多數(shù)程序都需要進行用戶跟蹤。用戶鏈接程序時需要進行身份認證,通過這一過程,讓程序知道自己的身份。程序知道用戶是誰...
    藕絲空間閱讀 1,098評論 0 0
  • ? Flask-Login: 管理已登錄用戶的用戶會話。? Werkzeug: 計算密碼散列值并進行核對。? it...
    huozhihui閱讀 1,346評論 0 2
  • 前言 最近在看Flask Web開發(fā),感覺這本書寫的真不錯,里面教開發(fā)者如何一步步開發(fā)一個博客系統(tǒng)。剛開始看的時候...
    happyte閱讀 7,105評論 3 14

友情鏈接更多精彩內(nèi)容