flask-login原理详解
最近发现项目中使用的flask-login中有些bug,直接使用官网的方式确实可以用,但仅仅是可以用,对于原理或解决问题没有什么帮助,最近通过查看网上资料、分析源码、通过demo、从零开始总结了flask-login的使用方式及内部实现原理。
先说使用,安装组件就不说了,太简单。
安装好了之后就是把组件注册到flask中,这个简单说下,具体flask如何注册这些扩展的原理后续再补上,引用官网的说法:登录管理(login manager)包含了让你的应用和 flask-login 协同工作的代码,比如怎样从一个 id 加载用户,当用户需要登录的时候跳转到哪里等等。具体注册代码如下:。
# encoding:utf-8 from flask.ext.login import loginmanager app = flask(__name__) login_manager = loginmanager() login_manager.init_app(app)
login_manager.login_view = "login" #配置如果需要登录调整的路由
注册好了之后,就可以使用了,以下是路由函数的写法,具体login_required、login_user的实现原理后面会再说
@app.route('/') @login_required #进入首页需要判断用户是否登入,没有登录则跳转到注册时配置的路由 def index(): return render_template('index.html') @app.route('/login', methods = ['post', 'get']) def login(): if request.method == 'post': req = request.get_json() username = req['username'] userpassword = req['userpassward'] if auth_user(username, userpassword): #判断用户名密码是否正确,这里随便写一个方法,写死用户名密码就可以 login_user(user(14,'root', 'root')) #用户名密码验证通过调用login_user把user注册到请求上下文session中,这个session其实就是一个localstack return url_for('index') flash(u'无效的用户名或密码') return render_template('login.html')
还差一部分,创建models模块,用处两个,一个用来定义user类,二用来注册回调函数,这个回调函数通过user_id返回user实例。这里只是想弄清楚login的原理所有没用数据库,尽量聚焦
from flask.ext.login import usermixin from app import login_manager class user(usermixin): __tablename__ = 'users' def __init__(self, id,password, username): ''' :param id: :param username: ''' self.id = id #这个属性一定要有,否则自己要重写get_id方法,不信自己去试下 self.password = password self.username = username def __repr__(self): return '<user %r>' % self.username @login_manager.user_loader def load_user(user_id): print user_id return user(14, 'root', 'root')
ok,基本使用完成了,前端再写两个简单页面就可以index.html和login.html。代码如下,这里主要理解后端流程,前端能用就可以
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>hello, {{ name }}</title> </head> <body> <form name="myform"> <label>用户名:</label> <input type="text" name="fname"/> <br> <label>密码:</label> <input type="text" name="address"/> <button type="button" onclick="login()">提交</button> </form> <script src="https://unpkg.com/axios/dist/axios.min.js"></script> <script> function login() { axios({ method: 'post', url: '/login', data: { username: myform.fname.value, userpassward: myform.address.value } }).then(function (response) { location.href = response.data }).catch(function (error) { console.log(error); }); } </script> </body> </html>
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>title</title> </head> <body> ceshi <button type="button" onclick="login()">提交</button> </body> <script src="https://unpkg.com/axios/dist/axios.min.js"></script> <script> {# 发送用户名、密码#} function login() { axios({ method: 'post', url: '/test', data: { username: 'c', userpassward: 'y' } }).then(function (response) { console.log(response.data); }).catch(function (error) { console.log(error); }); } </script> </html>
具体使用过程梳理完成。
再看看其实现的原理,后端路由接收到前端请求后,会进入login_require装饰器中,而此装饰器用来判断用户鉴权情况,如下图:
那用户登录鉴权的关键在装饰器@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
流程解析
1. 判断请求是否需要鉴权,current_app.login_manager._login_disabled,通常的命令get、post等请求都需要鉴权,此属性为false。
2. 判断当前用户是否鉴权,current_user.is_authenticated。current_user--------从哪获取?继续分析代码
current_user = localproxy(lambda: _get_user())
current_user通过loaclproxy创建了一个无名函数_get_user,为什么用lambda:_get_user()而不直接使用_get_user?我也没想明白,有牛人可以解释一下。
loaclproxy具体可以参考,说白了就是理解为把方法地址传给变量,以后可以动态调用代理方法
获取current_user通过_get_user()函数,先看下该函数的代码
def _get_user(): if has_request_context() and not hasattr(_request_ctx_stack.top, 'user'): current_app.login_manager._load_user() return getattr(_request_ctx_stack.top, 'user', none)
解释一下,首先需要知道请求上下文,代码中的_request_ctx_stack.top中是否有user这个变量,如果没有则_load_user,如果有直接取这个user属性返回给前面的说用来鉴权使用。
_load_user会调用reload_user函数,
def reload_user(self, user=none): ctx = _request_ctx_stack.top if user is none: user_id = session.get('user_id') if user_id is none: ctx.user = self.anonymous_user() else: if self.user_callback is none: raise exception( "no user_loader has been installed for this " "loginmanager. add one with the " "'loginmanager.user_loader' decorator.") user = self.user_callback(user_id) if user is none: ctx.user = self.anonymous_user() else: ctx.user = user else: ctx.user = user
该函数判断_request_ctx_stack.top赋值user对象,对象可以传入,也可以使用我们在使用过程中定义的def load_user(user_id),这个user_callback就是我们定义的load_user函数。
获取user如图
登入时和重新请求时都会将user放入到栈中,每次请求后都出清理top中的user。放入top时会设置user_id到session中。
上一篇: Emergency Evacuation(最短下车时间)———(思维)
下一篇: 设计模式之外观模式