[Python][flask][flask-login]關於flask-login中各種API使用實例


本篇博文跟上一篇[Python][flask][flask-wtf]關於flask-wtf中API使用實例教程有莫大的關系。

 

簡介:Flask-Login 為 Flask 提供了用戶會話管理。它處理了日常的登入,登出並且長時間記住用戶的會話。

        直白的講,flask-login包為用戶管理了涉及到用戶登錄相關的緩存(Session)管理。

 

Posted by Alima | cnblogs.

一.安裝(Install)

PC環境:Windows 7,Python 3.5.2。

PS:此次配置環境階段和上一篇博文中寫的一致,如果看了上一篇博文,安裝階段可以直接跳過。

  • 創建wtfdemo虛擬運行環境

用控制台(管理員運行模式)進入(cd)到想要創建工程的路徑下,創建wtfdemo文件夾。

mkdir wtfdemo

進入(cd)wtfdemo文件夾,創建Python虛擬運行環境。

virtualenv flaskr

出現如下字樣,說明虛擬環境創建成功

PS:本次提供第二種創建Python虛擬運行環境的使用方法

virtualenv -p source_file\python.exe target_file

為什么提出第二種創建方法,你會發現,當你的Python Web程序開發多了以后,PC上難免安裝了很多版本的Python運行環境。

舉例:當PC上同時安裝了Python2.7和Python3.5,運行virtualenv flaskr后,建立的Python虛擬運行環境是Python2.7版本的,但是我們的開發環境是Python3.5。

在控制台中輸入一下指令,你就會發現問題。

virtualenv -h

出現下面圖片顯示,默認的virtualenv安裝路徑是Python2.7,也就是默認的安裝的虛擬環境是Python2.7版本。

所以,在這種情況下,請指定你需要的Python版本,並建立虛擬運行環境。

  • 安裝flask-wtf庫文件

進入Python虛擬運行環境(在上一篇博文中寫過),執行以下指令。

pip install flask
pip install flask-wtf
pip install flask-login
pip install flask-sqlalchemy

出現如下圖所示,說明安裝成功。

flask安裝成功。

flask-wtf安裝成功。

flask-login安裝成功。(本次使用flask-wtf庫時需要輔助以該運行庫)

flask-sqlalchemy安裝成功。

至此,環境配置階段結束。

 

二.flask-Login介紹

它會:

①將活躍用戶的ID儲存在緩存(Session)中,讓登錄和注銷更加簡單。
②讓你對登入(或登出)的用戶限制瀏覽不同的視圖
③處理略棘手的“記住用戶”功能
④幫助保護使用用戶的緩存(Session),以免被惡意盜用
⑤可能與Flask-Principal或其他授權擴展,在以后的工作中進行整合

但是,它不會:

①強制讓你使用一個特定的數據庫或者其他的存儲方法。你可以完全負責你的用戶時如何加載的。
②限制你使用用戶名和密碼,OpenID或是其他的認證方法。
③處理“登入或登出”以外的權限
④處理用戶注冊或者賬戶恢復

總結,flask-Login包只管理用戶登入登出的緩存(Session)管理,而不做過多的超出自己權限的功能。

 

三.flask-login下的API介紹

由於flask-login中的API比較少,在本文中,盡可能將所有的API功能介紹列在這里。

  • LoginManager
class flask_login.LoginManager(app=None, add_context_processor=True)

這個類是用來保存用戶的登錄狀態的,也是flask-login包的主類。

官方:LoginManager的實例是“不”綁定到特定的應用程序的,所以你可以創建LoginManager對象在你的代碼主體上,所以你可以創建應用程序在工廠函數中。

解析:我們首先看LoginManager類的構造函數

__init__(self, app=None, add_context_processor=True)

該構造函數提供了一個缺省的局部變量app,默認值為None.和上文說道的,為“不綁定到特定的應用程序”解釋說明。也就是說,你可以直接定義LoginManager的實例,而不必去綁定到應用程序上才可以實例化。當你在代碼主題中定義了app后,可以隨時綁定到LoginManager上。

該構造函數初始化了一些列變量。

'''
LoginManager構造函數
Blog: www.cnblogs.com/alima/ Editor: Alima | cnblog
''' #: 提供了一個游客用戶,該用戶是在沒有登錄狀態下時使用 self.anonymous_user = AnonymousUserMixin #: 提供當用戶需要登錄時的重定向頁面參數 #: (此處也可以是一個絕對路徑) self.login_view = None #: 提供一個藍圖,當用戶需要登錄時的重定向頁面 #: 如果鍵值為空,則默認重定向到login_view下的地址 self.blueprint_login_views = {} #: 這條消息將flash在重定向頁面上 self.login_message = LOGIN_MESSAGE #: login_message的類型,默認LOGIN_MESSAGE_CATEGORY,可自定義 self.login_message_category = LOGIN_MESSAGE_CATEGORY #: 如果需要一個活躍的登錄,使用戶重定向到其他界面,重新驗證登錄 self.refresh_view = None #: 這條消息將flash到用戶重新驗證的界面上 self.needs_refresh_message = REFRESH_MESSAGE #: needs_refresh_message的類型,默認REFRESH_MESSAGE_CATEGORY,可自定義 self.needs_refresh_message_category = REFRESH_MESSAGE_CATEGORY #: 緩存(Session)的保護等級 #: 有三種等級,分別為: 'basic'(默認),'strong','None'(關閉緩存保護功能) self.session_protection = 'basic' #: 如果存在,則用來轉換使用login_message和needs_refresh_message self.localize_callback = None #: 檢索用戶對象回調 self.user_callback = None #: 未經授權的用戶回調(未登錄狀態) self.unauthorized_callback = None #: 未經授權的用戶回調(需要活躍登錄狀態) self.needs_refresh_callback = None #: 默認屬性以檢索用戶的Unicode標識(源碼注釋在flask_login.config文件下) self.id_attribute = ID_ATTRIBUTE #: 用於回調檢索用戶對象。(設置令牌狀態) self.header_callback = None #: 用於回調檢索用戶對象。(設置Flask請求對象狀態) self.request_callback = None #: 如果應用程序(app)為空,則默認創建一個空的LoginManager實例 if app is not None: self.init_app(app, add_context_processor)

API:init_app()

init_app(self, app, add_context_processor=True)

配置的應用程序。這將注冊一個'after_request`調用,並附加這個`LoginManager`把它作為'app.login_manager`。

這里滲透一下,三個flask架構自帶的裝飾器。

before_request :在請求收到之前綁定一個函數做一些事情。

after_request: 每一個請求之后綁定一個函數,如果請求沒有異常。

teardown_request: 每一個請求之后綁定一個函數,即使遇到了異常。

  • 重新定制你的用戶類

在之前的章節中,分別定義了不同的用戶類,flask-login包控制登入登出需要如下屬性。

① is_authenticated

當用戶通過驗證時,返回有效憑證True。

② is_active

一個活動用具有1)通過驗證 2)賬戶也已激活 3)未被停用 4)也不符合任何的應用拒絕登入條件,返回True。不活動的賬號無法登入。

③ is_anonyous

如果是一個匿名用戶,返回True,如果是登錄用戶則返回False

④ get_id()

返回一個能唯一識別用戶的,並能用於從user_loader回調中加載用戶的ID,這個ID必須是Unicode

如果你對你的新定制的用戶類沒有其他的要求,你可以繼承flask_login.mixins下定義的UserMixin作為你的用戶類的父類。

  • flask-login下的API詳解

① 配置登錄

API:@user_loader

解析:這個API設置一個回調,用於從緩存中加載用戶登錄信息。你構造的函數需要設置一個user ID並且返回一個用戶實體。如果用戶不存在則返回None。

使用舉例(簡單實現了一個加載用戶的操作,並沒有對入口變量做判斷):

'''
Editor: Alima | cnblog
'''
@login_manager.user_loader
def load_user(id):
    user = User.query.filter_by(id=id).first()
    return user

API:@request_loader(@header_loader)

解析:設置一個從flask請求加載用戶的回調。你需要給函數一個Flask請求,函數返回用戶實體,用戶不存在則返回None。

使用實例:(來自官方文檔的一段代碼,很有啟迪,這段代碼歡迎探討博主理解不是很好。

'''
Blog: www.cnblogs.com/alima/
From: Offical Doc
''' @login_manager.request_loader def load_user_from_request(request): # first, try to login using the api_key url arg api_key = request.args.get('api_key') if api_key: user = User.query.filter_by(api_key=api_key).first() if user: return user # next, try to login using Basic Auth api_key = request.headers.get('Authorization') if api_key: api_key = api_key.replace('Basic ', '', 1) try: api_key = base64.b64decode(api_key) except TypeError: pass user = User.query.filter_by(api_key=api_key).first() if user: return user # finally, return None if both methods did not login the user return None

API:anonymous_user

解析:提供了一個游客用戶,該用戶是在沒有登錄狀態下時使用

使用實例:(你可以判斷當前用戶的is_anonymous屬性,是不是True來看是不是隨機用戶)

if current_user.is_anonymous == True

② 未經授權的配置

API: flask_login.login_view

解析:當用戶需要登錄時,將用戶重定向到的界面。

使用舉例(將非法登錄強制重定向到登錄界面):

login_manager.login_view = 'login'

API: flask_login.login_message

解析:當用戶重定向時,flash出的消息。

使用舉例:

login_manager.login_message = 'Please enter your account'

API:@unauthorized_handler

解析:如果你不想用默認的重定向定制你的登錄,你可以在代碼視圖實體中顯示的寫出一個函數體,將@unauthorized_handler裝飾器添加到函數體之上。這樣做之后,當你用@login_required阻止用戶非法登錄,將執行我們新定義的函數體中的內容。

使用舉例(將非法登錄強制重定向到登錄界面):

'''
Editor: Alima | cnblog
'''
@login_manager.unauthorized_handler
def unauthorized_handler(): return redirect('/login')

③需要活躍登錄的配置

API: flask_login.refresh_view

解析:當用戶需要活躍登錄時,將用戶重定向到的界面。

使用舉例(將非法登錄強制重定向到登錄界面):

login_manager.refresh_view = 'login'

 API: flask_login.needs_refresh_message

解析:當需要活躍登錄的用戶重定向時,flash出的消息。

使用舉例:

login_manager.needs_refresh_message = 'You need a fresh log in.Please enter your account' 

API:@needs_refresh_handler

解析:作用和unauthorized_handler類似,當你需要登錄的用戶是輸入用戶名密碼登錄時,而你又不想使用默認的重定向方法,那么,你可以顯示的定義你自己處理函數。

使用舉例(將非活躍登錄強制重定向到登錄界面,並flash出消息):

 
         
'''
Editor: Alima | cnblog
'''
@login_manager.needs_refresh_handler
def
refresh(): flash('You should log in!') return logout()

 ④登錄機制

API:flask_login.current_user

解析:獲取當前緩存中保存的用戶帳戶信息(一個當前用戶的代理)

API:flask_login.login_fresh()

如果當前用戶是活躍登錄,則返回True。

API:flask_login.login_user(userremember=Falseforce=False,fresh=True)

解析Ⅰ:登錄用戶。你需要向函數中傳進一個用戶對象。如果用戶'is_active'屬性為'Flase',只有當'force'是'True'時才會允許登錄。當登錄成功后返回'True',當失敗時返回'False'。

           這里特別提一下函數參數'fresh',當設置為'False'時,將登陸用戶的緩存(Session)中標志為不是活躍登錄。默認值為'True'

舉例:

@app.route('/post')
@login_required
def post(): pass

解析Ⅱ:如果只有當前時刻你需要要求你的用戶登錄,你可以這樣做:

if not current_user.is_authenticated:
    return current_app.login_manager.unauthorized()

          實際上,將上邊的代碼添加到你的視圖中去就可以達到要求了。

          當單體測試的時候它可以很方便的全局關閉認證。將'LOGIN_DISABLED'設置為True,裝飾器就會被忽略了。

API:flask_login.logout_user()

解析:用戶登出(你不需要通過實際用戶)。這個函數同時會清除remember me中的cookie(如果存在的話)。

        作用就是清除一系列的cookie信息。

API:flask_login.confirm_login()

解析:函數將會將當前Sesslion設置為活躍。當從cookie加載之后,Sesson會變成不活躍狀態。

⑤保護視圖

API:flask_login.login_required

解析:如果你將這個裝飾器放在視圖上,它會保證你的當前用戶是登錄狀態,並且在調用實際視圖之前進行認證。

        如果當前用戶不是系統認證的登錄狀態,它將調用LoginManager.unauthorized回調。

API:flask_login.fresh_login_required

解析:如果用這個裝飾器放在視圖上,它將確保當前用戶是活躍登錄的。用戶的Session不是從'remember me'的cookie中加載的。

        敏感的操作,例如修改密碼或者郵箱,應該用這個裝飾器保護,來阻止cookie被盜取。

        如果用戶沒有認證,通常調用'LoginManager.unauthorized'。

        如果用戶被認證了,但是緩存不是活躍登陸,它將會'LoginManager.needs_refresh'代替,而且你需要提供一個'LoginManager.refresh_view'。

幾乎所有的flask-login下的API都在本文介紹了一下,其中@request_loader裝飾器,博主理解不是很好,一旦有進展,會更新在文章中。如果你知道@request_loader裝飾器的用法,歡迎私信評論給博主。

以上就是API的介紹,我們下面來看實例。

 

四.應用實例

本次在前一篇博文的基礎上,我們來展開本篇博文。

首先,我們要介紹model.py類,以便匹配本次的flask-login。

'''
File name: model.py
Editor: Alima | cnblogs
Blog:   www.cnblogs.com/alima/
'''

from wtf import db
from flask_login import UserMixin

class User(db.Model, UserMixin):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True)
    password = db.Column(db.String(80), unique=True)
    
    def __init__(self, username, password):
        self.username = username
        self.password = password
        
    def __repr__(self):
        return '<User %r>' % self.username

根據我們講到的,使用flask-login驗證用戶登錄時需要如下屬性。

① is_authenticated

② is_active

③ is_anonyous

④ get_id()

User繼承了UserMixin類的屬性,方法。下面附加上flask-login包中的UseMixin類的源碼。

'''
From Github: maxcountryman/flask-login '''

class UserMixin(object):
    '''
    This provides default implementations for the methods that Flask-Login
    expects user objects to have.
    '''

    if not PY2:  # pragma: no cover
        # Python 3 implicitly set __hash__ to None if we override __eq__
        # We set it back to its default implementation
        __hash__ = object.__hash__

    @property
    def is_active(self):
        return True

    @property
    def is_authenticated(self):
        return True

    @property
    def is_anonymous(self):
        return False

    def get_id(self):
        try:
            return text_type(self.id)
        except AttributeError:
            raise NotImplementedError('No `id` attribute - override `get_id`')

    def __eq__(self, other):
        '''
        Checks the equality of two `UserMixin` objects using `get_id`.
        '''
        if isinstance(other, UserMixin):
            return self.get_id() == other.get_id()
        return NotImplemented

    def __ne__(self, other):
        '''
        Checks the inequality of two `UserMixin` objects using `get_id`.
        '''
        equal = self.__eq__(other)
        if equal is NotImplemented:
            return NotImplemented
        return not equal

附加屬性一目了然,如果你想重新定制你的用戶類,那么請重新類中的方法重寫這個方法,如果仍想調用父類UserMixin中的方法,請使用super()語句。

這下,就解釋了之前沒有提到的UserMixin的作用。

下面來解釋上次有很多有疑點的view.py,並在其中加入本文介紹的一些API的使用。

 1 '''
 2 File Name: view.py
 3 Editor: Alima | cnblogs
 4 Blog: www.cnblogs.com/alima/
 5 '''
 6 
 7 from flask import render_template, flash, redirect, session, url_for, request, g
 8 from flask_login import login_user, logout_user, current_user, login_required, AnonymousUserMixin, fresh_login_required, login_fresh
 9 from wtf import app, db, lm, User
10 from form import LoginForm, UploadForm
11 
12 @app.before_request
13 def before_request():
14     g.user = current_user
15  
16 @lm.user_loader
17 def load_user(id):
18     user = User.query.filter_by(id=id).first()
19     return user
20 
21 @app.route('/login', methods=['GET', 'POST'])
22 def login():
23     if g.user is not None and g.user.is_authenticated:
24         #login_fresh()
25         session['_fresh'] = False
26         return redirect(url_for('index'))
27     if g.user.is_active == True:
28         flash('active is True')
29     else:
30         flash('active is False')
31     form = LoginForm()
32     if form.validate_on_submit():
33         user = User.query.filter_by(username=form.username.data, password=form.password.data).first()
34         if(user is not None):
35             login_user(user,remember=form.remember_me.data)
36             return redirect(request.args.get("next") or url_for('index'))
37     return render_template('login.html', form=form)
38     
39 @app.route('/', methods = ['GET', 'POST'])
40 @app.route('/index', methods=['GET', 'POST'])
41 @login_required
42 def index():
43     form = UploadForm()
44 
45     if form.validate_on_submit():
46         filename = secure_filename(form.file.data.filename)
47         form.file.data.save('uploads/' + filename)
48         return redirect(url_for('upload'))
49 
50     return render_template('upload.html', form=form)
51     
52 @app.route('/logout')
53 @login_required
54 def logout():
55     logout_user()
56     return redirect(url_for('login'))

解釋:

①第14行,current_user是在當前緩存中取出的用戶實體,如果存在用戶登錄的緩存則返回實體,如果Session不存在則返回None。

②第16行,@lm.user_loader,這個裝飾器表示,每次有請求時,都會調用它所裝飾的函數。

Q: user_loader的運行機制是什么呢?

A: 答案在stackoverflow上有一個最好的解釋,附上連接:flask-login: can't understand how it works

    解釋一下答案中的最后幾個點,

    1)你需要由你自己寫出這段代碼。,檢查用戶名和密碼是否匹配(非請求到數據庫的情況下)。

    也就是說,用戶的cookie中可以存在非法的登錄信息,你需要自己寫出代碼校驗是否符合你所用數據庫的格式。

    2)如果認證成功,取得用戶ID,然后將用戶實體它傳遞給login_user()。

③第35行,login_user()博主准備放在左后寫,這是flask-Login的精髓,也是很多人不了解的。

④第41行,@login_required裝飾器將驗證所裝飾的函數是否是在用戶登錄狀態下訪問的。

   我們在配置文件(wtf.py)中配置如下信息,指定了當用戶非法登錄時重定向的界面和flash出的消息。

lm.login_view = 'login'
lm.login_message = 'Please log in!'
lm.login_message_category = 'info'

 ⑤第25行,session['_fresh'] = False沒看懂,請Ctrl+F在本文中搜索,下面有解釋。

讓我們之間訪問http://127.0.0.1:5000/index會發生什么。

看到紅色框之內的信息了么,就是定義的login_message中的信息。同時頁面跳轉到了http://127.0.0.1:5000/login,也就是定義的login_view。

 同時,上文還講到使用unauthorized_handler裝飾器,可以定制你自己的非法登陸處理過程。這里我們做一個簡單的使用過程,非法登錄時,頁面將顯示You have not logged in,你可以自己體驗其中的妙處。

@lm.unauthorized_handler
def unauthorized_handler():
    return 'You have not logged in!'

 下面,讓我們新加入一個需要活躍登錄的界面。

Q:什么是活躍登錄(fresh login)?

A:活躍登錄是指當用戶通過鍵入賬戶,並點擊登錄按鈕后的登錄狀態。

     而當用戶通過緩存(Session)登錄時,被認為是非活躍登錄狀態。

Q:為什么區分活躍登錄和非活躍登錄?

A:當用戶需要修改用戶信息,密碼或是其他隱私信息的時候,防止你的PC被別人登錄並惡意修改,或是其它人盜用你的緩存達到你不期望的一些惡意操作。

在view.py中添加如下代碼段。

@app.route('/info', methods = ['GET', 'POST'])
@fresh_login_required
def info():
    return 'Edit your infomation'

在wtf.py中配置如下信息。

lm.refresh_view = 'login'
lm.needs_refresh_message = 'Please enter your info'
lm.needs_refresh_message_category = "refresh_info" 

如果想將頁面重定向到login界面,需要做點小的改善,就是修改flask-login源碼

文件在flaskr\Lib\site-packages\flask_login.py。

修改源碼中的needs_refresh()函數。

 1 def needs_refresh(self):
 2     '''
 3     This is called when the user is logged in, but they need to be
 4     reauthenticated because their session is stale. If you register a
 5     callback with `needs_refresh_handler`, then it will be called.
 6     Otherwise, it will take the following actions:
 7 
 8     - Flash :attr:`LoginManager.needs_refresh_message` to the user.
 9 
10     - Redirect the user to :attr:`LoginManager.refresh_view`. (The page
11       they were attempting to access will be passed in the ``next``
12       query string variable, so you can redirect there if present
13       instead of the homepage.)
14 
15     If :attr:`LoginManager.refresh_view` is not defined, then it will
16     simply raise a HTTP 401 (Unauthorized) error instead.
17 
18     This should be returned from a view or before/after_request function,
19     otherwise the redirect will have no effect.
20     '''
21     user_needs_refresh.send(current_app._get_current_object())
22 
23     if self.needs_refresh_callback:
24        return self.needs_refresh_callback()
25 
26     if not self.refresh_view:
27         abort(401)
28 
29     if self.localize_callback is not None:
30        flash(self.localize_callback(self.needs_refresh_message),
31            category=self.needs_refresh_message_category)
32     else:
33            flash(self.needs_refresh_message,
34                   category=self.needs_refresh_message_category)
35     
36     logout_user() #Edit by Alima | cnblogs
37     return redirect(login_url(self.refresh_view, request.url))
View Code

Q:為什么要更改needs_refresh()函數?

A:試想當你進入info界面,info界面重定向到login界面,由於瀏覽器保留了用戶的登錄緩存,又由login界面跳轉到index界面。

    最后當你進入info界面的時候,現在不是活躍登錄,你預想的結果是讓用戶登錄,其實不然,你發現你的瀏覽器進入了index界面。

    所以你需要重新定制一下needs_refresh()函數,先將當前用戶退出登錄,這樣就會跳轉到login界面了,一個小小的思維陷阱。

    在源碼中沒有實現我們想要的效果,那么就修改源碼。

Q:當我看到view.py中,login函數時,有一個session['_fresh'] = False語句看不懂。

A:flask-login包,並沒有在用戶非活躍登錄狀態,將session['_fresh']設置為Flase的地方,所以我們在那里顯示的設置為Flase。

 

分別用兩種方式訪問http://127.0.0.1:5000/info

①在鍵入用戶賬戶並登錄成功的情況下,在瀏覽器中直接輸入127.0.0.1:5000/index和127.0.0.1:5000/info

②關閉瀏覽器界面,打開一個新的瀏覽器界面再次分別訪問127.0.0.1:5000/index和127.0.0.1:5000/info

結果:

①在鍵入用戶賬戶並登錄成功的情況下,兩個界面都能夠正常顯示。

②關閉瀏覽器界面,打開一個新的瀏覽器界面。可以訪問127.0.0.1:5000/index,但當訪問127.0.0.1:5000/info會跳轉到登錄界面,如下圖所示。

看到紅色的方框中,正是我們定義的needs_refresh_message信息,並且重定向到login界面。

相信讀到這里,你已經能夠使用:

①login_fresh()判斷當前是否是活躍登錄,是則返回True。

②confirm_login()將當前狀態強制轉換為活躍登錄。

 

最后的最后,我們解釋login_user()函數。

login_user()函數是flask-login的最核心的函數,附上源代碼。

 1 def login_user(user, remember=False, force=False, fresh=True):
 2     '''
 3     Logs a user in. You should pass the actual user object to this. If the
 4     user's `is_active` property is ``False``, they will not be logged in
 5     unless `force` is ``True``.
 6 
 7     This will return ``True`` if the log in attempt succeeds, and ``False`` if
 8     it fails (i.e. because the user is inactive).
 9 
10     :param user: The user object to log in.
11     :type user: object
12     :param remember: Whether to remember the user after their session expires.
13         Defaults to ``False``.
14     :type remember: bool
15     :param force: If the user is inactive, setting this to ``True`` will log
16         them in regardless. Defaults to ``False``.
17     :type force: bool
18     :param fresh: setting this to ``False`` will log in the user with a session
19     marked as not "fresh". Defaults to ``True``.
20     :type fresh: bool
21     '''
22     if not force and not user.is_active:
23         return False
24 
25     user_id = getattr(user, current_app.login_manager.id_attribute)()
26     session['user_id'] = user_id
27     session['_fresh'] = fresh
28     session['_id'] = _create_identifier()
29 
30     if remember:
31         session['remember'] = 'set'
32 
33     _request_ctx_stack.top.user = user
34     user_logged_in.send(current_app._get_current_object(), user=_get_user())
35     return True
View Code

Top One: 不關閉瀏覽器情況下的Remember me功能

Operation: 讀者可以嘗試一下,當我們運行本次實例時。

①當你登錄用戶,但不使用Remember me功能。

②不關閉瀏覽器,打開一個新的頁面,並將剛剛登錄過的界面關閉。

③在新的界面輸入127.0.0.1:5000/login

Explanation:

你會發現,你並沒有進入login界面,而是進入了index界面,你會想我不是沒有使用Remember me功能么?

其實,經常做Web程序的人會有直覺,問題出現在Session上,的確,確實發生在Session上。

以Chrome瀏覽器為例,打開Chrome菜單->設置->顯示高級設置->內容設置(隱私內容)->所有Cookie和數據庫數據(博主將截圖部分cookie和日期做了模糊處理,請見諒)

我們看到這個session的過期時間是在關閉瀏覽器之后,這樣就解釋了上面的現象。

當然,你可以設置cookie失效時間,下面表格是cookie的一些設置。

REMEMBER_COOKIE_NAME 存儲"Remember me"信息的Cookie名。默認值:remember_token
REMEMBER_COOKIE_DURATION cookie過期時間,是一個datetime.timedelta對象。默認值:365天
REMEMBER_COOKIE_DOMAIN 如果"Remember me"cookie需要跨域,在此設置域名值(eg: .example.com會允許example下所有子域名)。默認值:None
REMEMBER_COOKIE_PATH 限制"Remember me"的cookie存儲到某一路徑下。默認值:/
REMEMBER_COOKIE_SECURE 限制"Remember me"的cookie在某些安全通道下有用(HTTP)。默認值:None
REMEMBER_COOKIE_HTTPONLY 保護"Remember me"的cookie不能通過客戶端腳本訪問。默認值:False

 

 

 

 

 

TOP Two: 關閉瀏覽器后的Remember me功能。

Operation: 讀者可以嘗試一下,使用Remember me功能登錄。

①進入本次實例的登錄界面,輸入模擬用戶,勾選Remember me,點擊登錄按鈕登錄。

②關閉瀏覽器,重新進入本次實例系統

③你會發現你沒有進入預想的index界面,而是重新進入了login界面。

為什么?讓我們好好回憶一下,提示跟login_user()函數和類的初始化有關系。

對,你可以想到,LoginManager類的初始化是在一切操作之前,那么我們的當前用戶設置成了AnonymousUserMixin(游客),那我們不是做了login_user()操作了么。

讓我們回去看一下代碼。

 1 @app.route('/login', methods=['GET', 'POST'])
 2 def login():
 3     '''
 4     Alima | cnblogs
 5     '''
 6     api_key = request.args.get('api_key')
 7     if g.user is not None and g.user.is_authenticated:
 8         session['_fresh'] = False
 9         flash(current_app.login_manager._login_disabled)
10         flash(current_user.is_authenticated)
11         return redirect(url_for('index'))
12     form = LoginForm()
13     if form.validate_on_submit():
14         flash(form.remember_me.data)
15         user = User.query.filter_by(username=form.username.data, password=form.password.data).first()
16         if(user is not None):
17             login_user(user, form.remember_me.data)
18             return redirect(request.args.get("next") or url_for('index'))
19         else:
20             print(form.errors)
21     return render_template('login.html', form=form)

發現問題了么,這是因為博主之前了解flask-login包不是很深刻,以為@user_loader會使用戶自動load進LoginManager函數,其實不然。

將第8行改為:

login_user(load_user(g.user.id), True, False, False)

Q:為什么不傳入g.user或者current_user?

A:傳入g.user或者current_user,會發出一個maximum recursion depth exceeded in comparison的錯誤信息。

     current_user不是一個純正的用戶類,它的返回值是一個Thread Local 對象。(解析:Flask的Context機制

current_user = LocalProxy(lambda: _get_user())

Topic Three:logout_user()函數功能。

logout_user()函數功能就是將緩存的用戶信息清楚,將Remember me標記位設置為清空狀態。

 1 def logout_user():
 2     '''
 3     Logs a user out. (You do not need to pass the actual user.) This will
 4     also clean up the remember me cookie if it exists.
 5     '''
 6 
 7     user = _get_user()
 8 
 9     if 'user_id' in session:
10         session.pop('user_id')
11 
12     if '_fresh' in session:
13         session.pop('_fresh')
14 
15     cookie_name = current_app.config.get('REMEMBER_COOKIE_NAME', COOKIE_NAME)
16     if cookie_name in request.cookies:
17         session['remember'] = 'clear'
18 
19     user_logged_out.send(current_app._get_current_object(), user=user)
20 
21     current_app.login_manager.reload_user()
22     return True
View Code

 

至此,本篇介紹基本結束,更深入的flask-login包的使用,博主將在以后為大家開設專題深入講解。

如果你對博文中某些觀點,某些思考角度不一樣的,歡迎在Alima的cnblogs下面留言私信,你們的互動是我的動力。

Posted by Alima | cnblogs。

If there is some question Obsession about this blog,welcome to enter www.cnblogs.com/alima/ and seed message to me.

參考:

[1] flask-login Github maxcountryman https://github.com/maxcountryman/flask-login

[2] flask-login Doc maxcountryman https://flask-login.readthedocs.io/en/latest/

 

PS:

本篇博文撰寫時間比之前的已發表的幾篇博文相比,時間較長。但內容相比之前更充實,解析並修改源碼是一種新的嘗試。

學習新知識本應如此,追求一些細致的東西,會讓一些未知的問題迎刃而解。

如果你喜歡Alima的博文,歡迎Follow Alima的cnblogs的最新動態。

另外,Alima在謀求一個進取的平台發展,一份安心的Offer,歡迎投來橄欖枝,歡迎提出職位需求,請您聯系我。

----------------------------------

Alima的聯系方式

QQ Chat: 995816845

E-mail:xoxo2191@163.com

----------------------------------

 

 

*本文為Alima原創,轉載注明格式[轉載][博客園][Alima][關於flask-login中各種API使用實例],並在文首注明本文鏈接,多謝合作。

*非法轉載及非法抄襲博文將依照網絡著作權流程辦理,請尊重作者勞動成果,最終解釋權歸Alima與博客園共同所有,感謝合作。

*關於惡意爬蟲與刪除關於博主信息的原文進行轉載,請您高抬貴手,分享無價,別讓新博主對這一行失去興趣,營造良好的互聯網環境。

 

Power by Alima | cnblogs。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM