Flask 第三方组件之 login

在了解使用Flask来实现用户认证之前,我们首先要明白用户认证的原理。假设现在我们自己去实现用户认证,需要做哪些事情呢?

  1. 首先,登录。用户能够输入用户名和密码进行登录,所以需要网页和表单,实现用户输入和提交的过程。
  2. 接着,校验登录是否成功。用户提交了用户名和密码,后台需要比对用户名密码是否正确,而要想比对,首先系统中就要有存储用户名密码的地方,大多数后台系统会通过数据库来存储,也可以存储到文件当中。存储用户名密码需要加密存储尤其是密码,如果只是简单的用明文存储,很容易被“有心人”盗取,从而造成用户信息泄露
  3. 登录之后,我们需要维持用户登录状态,以便用户在访问特定网页的时候来判断用户是否已经登录,以及是否有权限访问改网页。这需要维护一个会话来保存用户的登录状态和用户信息。
  4. 从第三步我们也可以看出,如果我们的网页需要权限保护,那么当请求到来的时候,我们首先要检查用户的信息,比如是否已经登录,是否有权限等,如果检查通过,那么在response的时候就会将相应网页回复给请求的用户,但是如果检查不通过,那么就需要返回错误信息。
  5. 用户登出

flask通常是使用Flask-Login模块来实现上述流程控制。下面介绍使用Flask-Login登录注销,以及帮助大家解答一些可能比较常见的问题。

代码实现

首先,先概述下例子,有三个url,分别是:

/auth/login     用于登录
/auth/logout    用于注销
/test           用于测试,需要登录才能访问

安装必要的库

pip install Flask==0.10.1
pip install Flask-Login==0.3.2
pip install Flask-WTF==0.12
pip install WTForms==2.1

编写web框架。在开始登录之前,我们先把整个 web 的框架搭建出来,也就是,我们要能够先在不登录的情况下访问到上面提到的三个url,我就直接放在一个叫做 app.py 的文件中。

#!/usr/bin/env python
# encoding: utf-8
from flask import Flask, Blueprintapp = Flask(__name__)# url redirect
auth = Blueprint('auth', __name__)@auth.route('/login', methods=['GET', 'POST'])
def login():return "login page"@auth.route('/logout', methods=['GET', 'POST'])
def logout():return "logout page"    # test method
@app.route('/test')
def test():return "yes , you are allowed"app.register_blueprint(auth, url_prefix='/auth')
app.run(debug=True)

现在,我们可以尝试一下运行一下这个框架,使用 python app.py 运行即可,然后打开浏览器,分别访问一下,看一下是否都正常

http://localhost:5000/test
http://localhost:5000/auth/login
http://localhost:5000/auth/logout

设置登录才能查看。现在框架已经设置完毕,我们可以将 test 和 auth/logout 这两个 page 设置成登录之后才能查看。因为这个功能已经和 login 有关系了,所以这时我们就需要使用到 Flask-Login了。代码如下

#!/usr/bin/env python
# encoding: utf-8
from flask import Flask, Blueprint
from flask.ext.login import LoginManager, login_requiredapp = Flask(__name__)#################### 以下这段是新增加的 #################### 
app.secret_key = 's3cr3t'
login_manager = LoginManager()# 设置不同的安全等级防止用户会话遭篡改,属性可以设为None、basic或strong
# 设为 strong 时,Flask-Login 会记录客户端 IP 地址和浏览器的用户代理信息,如果发现异动就登出用户
login_manager.session_protection = 'strong' # 如果未登录,返回的页面
login_manager.login_view = 'auth.login'
login_manager.init_app(app)# Flask-Login 要求程序实现一个回调函数,使用指定的标识符加载用户。加载用户的回调函数接收以 Unicode 字符串形式表示的用户标识符。如果能找到用户,这个函数必须返回用户对象;否则应该返回 None,这里因为设置框架所以就默认返回 None。
@login_manager.user_loader
def load_user(user_id):return None
#################### 以上这段是新增加的 #################### auth = Blueprint('auth', __name__)@auth.route('/login', methods=['GET', 'POST'])
def login():return "login page"# 通过Flask-Login提供的login_required装饰器来增加路由保护,如果未认证用户访问这个路由,Flask-Login会将这个请求发往登录页面
@auth.route('/logout', methods=['GET', 'POST'])
@login_required
def logout():return "logout page"# test method
@app.route('/test')
@login_required
def test():return "yes , you are allowed"app.register_blueprint(auth, url_prefix='/auth')
app.run(debug=True)

其实我们就增加了两项代码,一项是初始化 LoginManager 的, 另外一项就是给 test 和 auth.logout 添加了 login_required 的装饰器,表示要登录了才能访问。注意 login_required 必须放在 auth.route 后面

#################### 部分源码 ####################
@app.route('/test', methods=['GET', 'POST'])
@csrf.exempt
@login_required
def test():pass
# test= app.route('/test', methods=['GET', 'POST'])(test)
# test= login_required(test)# login_required 源码
def login_required(func):@wraps(func)def decorated_view(*args, **kwargs):if current_app.login_manager._login_disabled:return func(*args, **kwargs)elif not current_user.is_authenticated:return current_app.login_manager.unauthorized()return func(*args, **kwargs)return decorated_view# app.route 实际最后执行代码
def app.route() if view_func is not None:old_func = self.view_functions.get(endpoint)if old_func is not None and old_func != view_func:raise AssertionError('View function mapping is overwriting an existing endpoint function: %s' % endpoint)self.view_functions[endpoint] = view_func#################### 分析 ####################
# 原因,正常情况下装饰器需要将函数地址传入并返回一个新的函数地址,但是 app.route 创建了一个新的结构并将传入的函数地址直接保存到结构中,导致其他的装饰器对这个函数地址修改影响不到 app.route 创建的结构,而在路由分发的时候,直接调用的是结构中保存的地址,所以其他装饰器不起作用,所以必须将装饰器放在 app.route 下面#################### 简化代码 ####################
def a():return 1def b():return 2c = a
a = b
print(c())

用户授权。到此,我们发现 test 是不能访问的,会被重定向到 login 的那个 page。看一下现在的代码, login_required 有了, 那么就差login了,接下来写login,看Flask-Login的文档发现一个叫做login_user的函数,看看它的原型:

flask.ext.login.login_user(user, remember=False, force=False, fresh=True)

这里需要一个user的对象,所以先创建一个Model,其实这个Model还是有一点讲究的,最好是继承自Flask-Login的UserMixin,然后需要实现几个方法,Model 为:

# user models
class User(UserMixin):def is_authenticated(self):return Truedef is_actice(self):return Truedef is_anonymous(self):return Falsedef get_id(self):return "1"

这里给所有的函数都返回了默认值,默认对应的情况是这个用户已经登录,并且是有效的。

然后在 login 的 view 里面 login_user, logout的view里面logout_user,这样整个登录过程就连接起来了,最后的代码是这样的:

#!/usr/bin/env python
# encoding: utf-8from flask import Flask,Blueprint
from flask.ext.login import LoginManager,login_required,login_user,logout_user,UserMixinapp = Flask(__name__)# user models
class User(UserMixin):def is_authenticated(self):return Truedef is_actice(self):return Truedef is_anonymous(self):return Falsedef get_id(self):return "1"# flask-login
app.secret_key = 's3cr3t'
login_manager = LoginManager()
login_manager.session_protection = 'strong'
login_manager.login_view = 'auth.login'
login_manager.init_app(app)@login_manager.user_loader
def load_user(user_id):user = User()return userauth = Blueprint('auth', __name__)@auth.route('/login', methods=['GET', 'POST'])
def login():user = User()login_user(user)return "login page"@auth.route('/logout', methods=['GET', 'POST'])
@login_required
def logout():logout_user()return "logout page"@app.route('/test')
@login_required
def test():return "yes , you are allowed"app.register_blueprint(auth, url_prefix='/auth')
app.run(debug=True)

总结

到此,这就是一个比较精简的Flask-Login 教程了,通过这个框架大家可以自行扩展达到更丰富的功能,诸如发送确认邮件,密码重置,权限分级管理等,这些功能都可以通过flask及其插件来完成,这个大家可以自己探索下。

问题

1、未登录访问鉴权页面如何处理

如果未登录访问了一个做了 login_required 限制的 view,那么 flask-login 会默认 flash 一条消息,并且将重定向到 login view, 如果你没有指定 login view, 那么 flask-login 将会抛出一个401错误。指定 login view 只需要直接设置login_manager即可:

login_manager.login_view = "auth.login"

2、自定义flash消息

login_manager.login_message = u"请登录!"       # 自定义 flash 的消息
login_manager.login_message_category = "info"  #  flash 消息的级别,一般设置成 info 或者 error

 

3、自定义未登录处理函数

如果你不想使用默认的规则,那么你也可以自定义未登录情况的处理函数,只需要使用 login_manager 的 unauthorized_handler 装饰器即可。

@login_manager.unauthorized_handler
def unauthorized():# do stuffreturn render_template("some template")

4、匿名用户是怎么处理的?有哪些属性?

在 flask-login 中,如果一个匿名用户访问站点,那么 current_user 对象会被设置成一个 AnonymousUserMixin 的对象,AnonymousUserMixin 对象有以下方法和属性:

  • is_active and is_authenticated are False
  • is_anonymous is True
  • get_id() returns None

5、自定义匿名用户Model:

如果你有需求自定义匿名用户的 Model,那么你可以通过设置 login_manager 的 anonymous_user 属性来实现,而赋值的对象只需是可调用对象(class 和 function都行)即可。

login_manager.anonymous_user = MyAnonymousUser

6、Flask-Login如何加载用户的:

当一个请求过来的时候,如果 ctx.user 没有值,那么 flask-login 就会使用 session 中 session['user_id'] 作为参数,调用 login_manager 中使用 user_loader 装饰器设置的 callback 函数加载用户,需要注意的是,如果指定的 user_id 无效,不应该抛出异常,而是应该返回 None。

登录成功后,就可以使用 current_use r对象了,current_user 保存的就是当前用户的信息,实质上是一个 User 对象,所以我们直接调用其属性, 例如这里我们要给模板传一个 username 的参数,就可以直接用 current_user.username

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

session['user_id'] 其实是在调用 login_in 函数之后自动设置的。

7、Flask-Login设置session过期时间:

在 Flask-Login 中,如果你不特殊处理的话,session 是在你关闭浏览器之后就失效的。也就是说每次重新打开页面都是需要重新登录的。如果你需要自己控制 session 的过期时间的话:

  • 首先需要设置 login_manager 的 session类型为永久的,
  • 然后再设置 session 的过期时间
#################### 配置文件 ####################
class Config:...PERMANENT_SESSION_LIFETIME = datetime.timedelta(minutes=5)#################### 登录 ####################
def login():login_user(user)session.permanent = True  # 设置session永久有效  注意这个要设置在request里边 即请求内部

同时,还需要注意的是 cookie 的默认有效期其实是 一年 的,所以,我们最好也设置一下:

login_manager.remember_cookie_duration=timedelta(days=1)

8、如何在同域名下的多个系统共享登录状态

这个需求可能在公司里面会比较常见,也就是说我们一个公司域名下面会有好多个子系统,但是这些子系统都是不同部门开发的,那么,我们如何在这不同系统间共享登录状态?也就是说,只要在某一个系统登录了,在使用其他系统的时候也共享着登录的状态,不需要再次登录,除非登录失效。

Server-side Sessions with Redis

这个说明尝试,也差不多是类似的解决方法。

9、使用Flask自带的函数加密存储密码

# models.pyfrom werkzeug.security import generate_password_hash
from werkzeug.security import check_password_hash
from flask_login import UserMixin
import json
import uuid# define profile.json constant, the file is used to
# save user name and password_hash
PROFILE_FILE = "profiles.json"class User(UserMixin):def __init__(self, username):self.username = usernameself.id = self.get_id()@propertydef password(self):raise AttributeError('password is not a readable attribute')@password.setterdef password(self, password):"""save user name, id and password hash to json file"""self.password_hash = generate_password_hash(password)with open(PROFILE_FILE, 'w+') as f:try:profiles = json.load(f)except ValueError:profiles = {}profiles[self.username] = [self.password_hash,self.id]f.write(json.dumps(profiles))def verify_password(self, password):password_hash = self.get_password_hash()if password_hash is None:return Falsereturn check_password_hash(self.password_hash, password)def get_password_hash(self):"""try to get password hash from file.:return password_hash: if the there is corresponding user inthe file, return password hash.None: if there is no corresponding user, return None."""try:with open(PROFILE_FILE) as f:user_profiles = json.load(f)user_info = user_profiles.get(self.username, None)if user_info is not None:return user_info[0]except IOError:return Noneexcept ValueError:return Nonereturn Nonedef get_id(self):"""get user id from profile file, if not exist, it willgenerate a uuid for the user."""if self.username is not None:try:with open(PROFILE_FILE) as f:user_profiles = json.load(f)if self.username in user_profiles:return user_profiles[self.username][1]except IOError:passexcept ValueError:passreturn unicode(uuid.uuid4())@staticmethoddef get(user_id):"""try to return user_id corresponding User object.This method is used by load_user callback function"""if not user_id:return Nonetry:with open(PROFILE_FILE) as f:user_profiles = json.load(f)for user_name, profile in user_profiles.iteritems():if profile[1] == user_id:return User(user_name)except:return Nonereturn Non
  • User类需要继承flask-login中的UserMixin类,用于实现相应的用户会话管理。

  • 这里我们是直接存储用户信息到一个json文件"profiles.json"

  • 我们并不直接存储密码,而是存储加密后的hash值,在这里我们使用了werkzeug.security包中的generate_password_hash函数来进行加密,由于此函数默认使用了sha1算法,并添加了长度为8的盐值,所以还是相当安全的。一般用途的话也就够用了。

  • 验证password的时候,我们需要使用werkzeug.security包中的check_password_hash函数来验证密码

  • get_id是UserMixin类中就有的method,在这我们需要overwrite这个method。在json文件中没有对应的user id时,可以使用uuid.uuid4()生成一个用户唯一id

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/454043.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Zookeeper客户端Curator使用详解

http://www.jianshu.com/p/70151fc0ef5dZookeeper客户端Curator使用详解 简介 Curator是Netflix公司开源的一套zookeeper客户端框架,解决了很多Zookeeper客户端非常底层的细节开发工作,包括连接重连、反复注册Watcher和NodeExistsException异常等等。Pat…

python argparse nargs_Python | 使用argparse解析命令行参数

今天是Python专题第27篇文章,我们来聊聊Python当中的命令行参数工具argparse。命令行参数工具是我们非常常用的工具,比如当我们做实验希望调节参数的时候,如果参数都是通过硬编码写在代码当中的话,我们每次修改参数都需要修改对应…

Python 第三方模块之 smtplib

1 python对SMTP的支持 SMTP(Simple Mail Transfer Protocol)是简单传输协议,它是一组用于用于由源地址到目的地址的邮件传输规则。 python中对SMTP进行了简单的封装,可以发送纯文本邮件、HTML邮件以及带附件的邮件。两个核心模块…

Node.js 使用jQuery取得Nodejs http服务端返回的JSON对象示例

server.js代码: // 内置http模块,提供了http服务器和客户端功能(path模块也是内置模块,而mime是附加模块) var httprequire("http");// 创建服务器,创建HTTP服务器要调用http.createServer()函数&#xff0c…

linux下gdb单步调试

用 GDB调试程序 GDB 概述 ———— GDB 是 GNU开源组织发布的一个强大的 UNIX下的程序调试工具。或许,各位比较喜欢那种图形界面方式的,像 VC、 BCB等 IDE的调试,但如果你是在 UNIX平台下做软件,你会发现 GDB这个调试工具有比 V…

svg 动画_根据AI导出的SVG path制作SVG线条动画

点击右上方红色按钮关注“web秀”,让你真正秀起来前言首先祝大家2019新年快乐,万事大吉,猪事顺利,阖家欢乐。前面文章SVG 线条动画基础入门知识学习到了基础知识,现在来给大家讲讲如何制作SVG 制作复杂图形线条动画。假…

MySQL提示Truncated incorrect DOUBLE value解决方法

“Truncated incorrect DOUBLE value”的解决方法主要是这两种: 1、修改了多个列的值而各列之间用逗号连接而不要用and 错误写法示例:update tablename set col1value1 and col2value2 where col3value3;正确写法示例:update ta…

一个完美的导航树

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns"http://www.w3.org/1999/xhtml" ><head><title>无标题页</title><…

自定义python框架_Python web 框架Sanic 学习: 自定义 Exception

Sanic 是一个和类Flask 的基于Python3.5的web框架&#xff0c;它使用了 Python3 异步特性&#xff0c;有远超 flask 的性能。编写 RESTful API 的时候&#xff0c;我们会定义特定的异常错误类型&#xff0c;比如我定义的错误返回值格式为&#xff1a;{"error_code": …

文字水印

using System.Threading.Tasks;using System.IO;using System.Drawing; public static int Shuy(string Sname,string fname) { try { Image image Image.FromFile(fname); Graphics gra Graphics.FromImage(image); String text Sname; Font font new Font("宋体&quo…

读书笔记2013第3本:《无价》

《无价》这本书是过年前买的&#xff0c;网络书店上写着“老罗推荐”&#xff0c;想着好像是在老罗哪一年的演讲里听过这本书&#xff0c;在豆瓣上评分7.9。读书是为了产生行动&#xff0c;读书时要提的4个问题&#xff0c;1&#xff09;这本书主要在谈些什么&#xff1f;2&…

Linux下的程序调试——GDB

无论是多么优秀的程序员&#xff0c;都难以保证自己在编写代码时不会出现任何错误&#xff0c;因此调试是软件开发过程中的一个必不可少的 组成部分。当程序完成编译之后&#xff0c;它很可能无法正常运行&#xff0c;或者会彻底崩溃&#xff0c;或者不能实现预期的功能。此时如…

圆锥曲线万能弦长公式_2020高考数学50条秒杀型公式与方法

考试马上就要到了&#xff0c;学姐整理了高考数学50条秒杀型公式和方法&#xff0c;希望能帮助考生们更好地攻克数学难关&#xff01;高考数学秒杀公式与方法一1&#xff0c;适用条件&#xff1a;[直线过焦点]&#xff0c;必有ecosA(x-1)/(x1)&#xff0c;其中A为直线与焦点所在…

Python 内置模块之 logging

日志的级别和适用情况 级别适用情况DEBUG详细信息&#xff0c;通常只在诊断问题时对其感兴趣INFO确认工作正常WARNING表示发生了意料之外的事或者在不远的将来会有问题&#xff08;比如磁盘空间低&#xff09;。软件依然正常工作ERROR由于一个更加严重的问题&#xff0c;软件不…

Memory barrier

待续 Memory barrier,是一种屏障和一类指令&#xff0c;在执行这个屏障指令前后&#xff0c;CPU或者编译器在内存操作上强制一个约束序列。CPU使用性能优化器可以导致执行代码的无序。在单一线程执行中&#xff0c;重排序内存操作通常不会被注意。但是在并行编程或者设备驱动中…

数据结构与算法 Python语言描述 笔记

数据结构 线性表包括顺序表和链表&#xff0c;python的list是顺序表&#xff0c;链表一般在动态语言中不会使用。不过链表还是会出现在各种算法题中。 链表 link list 单链表 逆转链表&#xff1a; leetcode 206双链表循环单链表字符串 string 有一个重要的点就是字符串的匹配问…

Flask 跨域问题

一、什么是跨域 跨域是指&#xff1a;浏览器A从服务器B获取的静态资源&#xff0c;包括Html、Css、Js&#xff0c;然后在Js中通过Ajax访问C服务器的静态资源或请求。即&#xff1a;浏览器A从B服务器拿的资源&#xff0c;资源中想访问服务器C的资源。 同源策略是指&#xff1a;…

Hibernate 中配置属性详解(hibernate.properties)

转自&#xff1a;https://blog.csdn.net/shudaqi2010/article/details/70324843 Hibernate能在各种不同环境下工作而设计的, 因此存在着大量的配置参数。多数配置参数都 有比较直观的默认值, 并有随 Hibernate一同分发的配置样例hibernate.properties 来展示各种配置选项。 所需…

1.3 使用电脑测试MC20的电话语音功能

需要准备的硬件 MC20开发板 1个https://item.taobao.com/item.htm?id562661881042GSM/GPRS天线 1根https://item.taobao.com/item.htm?id531979567261IPEX接口转SMA接口转接线 1根https://item.taobao.com/item.htm?id531979903836GPS有源天线 1根https://item.taobao.com/i…

前端之 AJAX

AJAX参数详细列表 参数名类型描述urlString(默认: 当前页地址) 发送请求的地址。typeString(默认: "GET") 请求方式 ("POST" 、 "GET")。注意&#xff1a;其它 HTTP 请求方法&#xff0c;如 PUT 和 DELETE &#xff0c;但仅部分浏览器支持。tim…