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:[email protected]


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


 


 


*本文为Alima原创,转载注明格式[转载][博客园][Alima][关于flask-login中各种API使用实例],并在文首注明本文链接,多谢合作。


*非法转载及非法抄袭博文将依照网络著作权流程办理,请尊重作者劳动成果,最终解释权归Alima与博客园共同所有,感谢合作。


*关于恶意爬虫与删除关于博主信息的原文进行转载,请您高抬贵手,分享无价,别让新博主对这一行失去兴趣,营造良好的互联网环境。


 


Power by Alima | cnblogs。

你可能感兴趣的:(flask)