在了解使用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

Flask 第三方组件之 login相关推荐

  1. 4.flask第三方组件

    1.flask-session的使用 在flask中,有一个app.session_interface = SecureCookieSessionInterface(),也就是存session,调用o ...

  2. Flask 第三方组件之 WTForms

    简介 WTForms是一个支持多个web框架的form组件,主要用于对用户请求数据进行验证. 安装: pip3 install wtforms 用户登录注册示例 1. 用户登录 当用户登录时候,需要对 ...

  3. Flask 第三方组件之 Migrate

    flask-migrate是flask的一个扩展模块,主要是扩展数据库表结构的.类似于Django的python manage.py migrate 官方文档: http://flask-migrat ...

  4. Flask 第三方组件之 script

    Flask Script扩展提供向Flask插入外部脚本的功能,包括运行一个开发用的服务器,一个定制的Python shell,设置数据库的脚本,cronjobs,及其他运行在web应用之外的命令行任 ...

  5. Flask 第三方组件之 SQLAlchemy

    一.介绍 SQLAlchemy是一个基于Python实现的ORM框架.该框架建立在 DB API之上,使用关系对象映射进行数据库操作,简言之便是:将类和对象转换成SQL,然后使用数据API执行SQL并 ...

  6. iOS 项目中用到的一些开源库和第三方组件

    iOS 项目中用到的一些 iOS 开源库和第三方组件 分享一下我目前所在公司 iOS 项目中用到的一些 iOS 开源库和第三方组件, 感谢开源, 减少了我们的劳动力, 节约了我们大量的时间, 让我们有 ...

  7. React Native 项目常用第三方组件汇总

    React Native 项目常用第三方组件汇总 https://www.jianshu.com/p/d9cd9a868764?utm_campaign=maleskine&utm_conte ...

  8. android多线程下载原理,安卓多线程断点续传下载功能(靠谱第三方组件,原理demo)...

    一,原生的DownloadManager 从Android 2.3(API level 9)开始,Android以Service的方式提供了全局的DownloadManager来系统级地优化处理长时间 ...

  9. 如何在Eclipse中查看Android源码或者第三方组件包源码

    文章出处:http://blog.csdn.net/cjjky/article/details/6535426 在学习过程中如果经常阅读源码,理解程度会比较深,学习效率也会比较高,那么如何方便快捷的阅 ...

最新文章

  1. Java局部变量final
  2. 一个具有多模型融合能力的网络或许是这样的
  3. python 猜数字大小
  4. C++阶段01笔记04【程序流程结构(选择结构、循环结构、跳转语句)】
  5. 程序设计中的驼峰原则
  6. 7.1 useradd:创建用户
  7. 常青:小程序音视频能力再升级
  8. 【hdu 4859】海岸线(图论--网络流最小割)
  9. 深交所再推跨市场ETF交易模式创新,助力ETF发展
  10. 后端如何接收对象类型的数据_javascript基本数据类型赋值和对象引用的内存情况分析...
  11. 计算机截图工具无法运行,win7运行截图工具提示截图工具未运行解决方法
  12. win10家庭版开启远程桌面(带rdpwrap.ini)
  13. SCI写作Response to reviewers 范例
  14. 网络攻防“三剑客”正式加盟墨者安全 担任首席安全顾问...
  15. 初学C语言的感受(张森)
  16. 阿里云短信服务接口触发天级流控Permits:10
  17. 极客时间 自我提升第二天 数据结构与算法之美 应该掌握 / 趣谈网络原理 / 深入浅出计算机组成原理 思维导图
  18. Games104 Lecture 7 游戏中渲染管线、后处理和其他的一切
  19. NJR日清纺微理光R5445系列 单节锂电池保护芯片,内置驱动器高位Nch FET开关温度保护
  20. 炫光鬼影原因分析转载

热门文章

  1. 男人该知道的人生感悟(图)
  2. C/C++开发者必不可少的15款编译器+IDE
  3. inline 内联函数详解 内联函数与宏定义的区别
  4. Eclipse中启动tomcat报错java.lang.OutOfMemoryError: PermGen space的解决方法
  5. 查看VMware上虚拟机的 ip 地址
  6. 洛谷 U3357 C2-走楼梯
  7. 调整linux系统时区
  8. 妈的我好像发现是哪出问题了
  9. ASP.NET下QueryString不同字符编码间强制转换的解决方案
  10. biztalk BLogs