flask 登录功能流程源码分析
Posted 胖虎是只mao
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了flask 登录功能流程源码分析相关的知识,希望对你有一定的参考价值。
如下图所示,这是我们在之前的实验中编写的登录视图函数,它在 handlers/front.py 文件中:
当我们点击登录提交按钮后,首先进行第 42 行的表单验证,通过后,根据邮箱查询 user 数据表中对应的用户信息,然后调用 Flask-Login 提供的 login_user
方法登录。
接下来我们继续分析这个方法,它在 flask_login.utils 模块中:
在上一节实验我们对这个方法的参数做了说明:
- user 就是 User 映射类的实例;
- remember 是布尔值,当用户在表单中选中「记住我」时,remember 参数的值为 True ;
- duration 是有效时间,也就是绿码的有效时间,在我们的项目中,它就是 session ,中文名字是「会话」;
- force 表示强制登录,当用户处于封禁状态时,user.is_active 属性值为 False 。这种情况下 force 参数的值为 True 的话,会强行执行登录逻辑;
- fresh 字面意思就是「新鲜」,对于敏感操作,比如修改密码,就需要用户处于新鲜的登录状态。例如用户登录时验证了信息,session 使得用户处于登录状态,但不是新鲜的登录状态。如果用户想修改密码,就需要重新验证一次信息,这次验证会设置 session ,使得本次验证后的用户处于新鲜的登录状态。这样起到对敏感操作的保护作用。
如上图所示,第 166 行处理 force 参数,判断是否强行登录。
第 169 行代码,current_app
就是应用对象,它的 login_manager
属性在前面的步骤中已经分析过,就是登录管理对象,LoginManager 类的实例。这个实例的 id_attribute 属性值是变量 ID_ATTRIBUTE ,这个变量来自配置文件 flask_login.config 模块,变量值是 'get_id'
。这一行等号前面的 user_id 变量值就是请求登录的用户在数据表中的 id 字段的值的字符串。
上节实验就介绍到这里,本节实验我们要继续了解后面的全部代码。
第 170 行 session['_user_id']
的值也就是 id 值的字符串了。第 171 行 session['_fresh']
的值默认是 True。
第 172 行需要着重说明,current_app.login_manager
就是登录管理类的实例,也就是 self ,所以后面的 _session_identifier_generator
就是在 flask_login.login_manager 模块中定义的属性,而这个属性的值就是当前模块下的 _create_identifier
函数:
如上图所示,第 367 行这里用到了请求头中的 User-Agent
字段,也就是用户代理字符串。
第 370 行 format 的第一个参数,_get_remote_addr
方法的返回值是请求的 IP 地址,所以等号前面的 base 就是用 | 隔开的一个字符串,它里面是请求的 IP 地址和用户代理。
最后利用 hashlib 模块提供的哈希功能将 base 转成十六进制哈希字符串并返回。这个字符串有什么用呢?
思考一下,我们在电脑上打开 A 浏览器登录某网站后,打开 B 浏览器再打开该网站,我们并未处于登录状态,只有 A 浏览器处于登录状态,为什么?因为服务器是依靠请求中的多项信息来识别用户,其中一项就是用户代理,A B 两个浏览器的用户代理字符串不同,就是真正的原因。
现在回到 login_user 方法的源码中,第 172 行 session['_id']
的值就是上述哈希字符串。
到这里为止,我们的 session 里已经有这些字段了:
- csrf_token :这是在创建表单类实例时添加的;
- _user_id :用户 ID 字符串;
- _fresh :设置新鲜的登录状态;
- _id :根据请求的 IP 和用户代理字符串哈希得到的字符串。
接下来,如果我们在登录前选中「记住我」,第 175 行就会再增加一个 _remember
,值是字符串 ‘set’ 。如果有 duration 参数的话,还会增加一个 _remember_seconds
字段。
继续向下,第 187 行 current_app 是 flask.globals 模块中定义的一个应用代理对象,可以把它看作应用对象,这个对象的 login_manager 属性就是 flask_login.login_manager 模块中定义的 LoginManager 类的实例,该实例的 _update_request_context_with_user
方法把 user 赋值给定义在 flask.ctx 模块中的请求上下文类 RequestContext 的实例的 user 属性,也就是说,请求上下文对象的 user 属性值就是当前要登录的用户对象。
这里需要提一下 app.py 文件中,创建登录管理对象时,从 flask_login.__init__
模块引入对象时,会运行 flask_login.utils 模块:
这里的 current_user 变量与 current_app 、g 、request 、session 一样,都是全局代理对象。注意第 26 行的参数是匿名函数,_get_user
方法定义如下:
如上图所示,第 349 行返回的是请求上下文对象的 user 属性值,也就是当前要登录的那个用户对象。
再次回到 login_user 方法中,第 188 行是一种信号机制,暂时略过。最后第 189 行返回布尔值 True 。
登陆之后
视图函数处理完毕后,和第一次请求一样,要调用应用对象的 full_dispatch_request
方法,它定义在 flask.app 模块中:
如上图所示,最后一行调用应用对象的 finalize_request
方法,此方法会调用 process_response
方法:
如上图所示,这些方法在前面的步骤中已经介绍过,这次我们看下第 2256 和 2257 行,这里有个 funcs ,它是一个迭代器,里面至少有一个 _update_remember_cookie
方法,此方法属于 flask_login.login_manager 模块中的 LoginManager 类,我们看下源码:
如上图所示,这里会从 session 中拿出 _remember
字段,如果在页面选择「记住我」,上图 407 行就会调用登录管理对象的 _set_cookie
方法,后者会调用响应对象的 set_cookie 方法设置一个 Set-Cookie 到响应头中。
登录管理对象的 _set_cookie
方法会从 session 中拿到 _remember_seconds
字段的值,它就是 Cookie 的有效期。如果没有这个字段,就会使用默认的设置,这个默认值就是一年。
最后把携带了 Set-Cookie 的响应对象返回给浏览器。
下次浏览器发起请求的时候,会带上 Cookies ,处理请求信息时,会调用令牌生成器将 session 字段对应的值进行解密获得一个字典对象,其中包括 _user_id(用户 ID)、csrf_token 、_id(浏览器识别)等重要键值对。利用这个字典对象生成请求上下文对象的 session 属性值,其实也就可以看做是 session 这个全局代理。最后利用 session 就可以判断该用户是否处于登录状态,Cookie 是否已过期。
这样就完成了登录功能的实现。
我们来整理一下 Flask 的登录流程:
- 浏览器首次发起登录请求:请求上下文对象的 session 属性值可以看做是 session 这个代理对象,它里面存储了请求头中的 Cookie 信息,包括
_fresh 和 csrf_token
这两个字段; - 调用视图函数来处理请求,向 session 中添加用户 ID
_user_id
和识别客户端身份的 _id 哈希值这两个字段,也可能会有 _remember 这个字段; - 视图函数处理完毕,如果 session 中有 _remember 字段,会单独取出并处理,向响应头中添加一个 Set-Cookie ;
- 然后处理 session ,将其进行整体哈希,向响应头中添加一个 Set-Cookie 。
此后浏览器再发起请求,就会携带上述 Cookies ,**服务器收到请求后就把这些信息用令牌生成器解密获得一个字典对象,并利用这个字典对象创建 session 。**最后根据 session 中的信息判断是哪位用户发来的请求以及该用户是否处于登录状态。
以上是关于flask 登录功能流程源码分析的主要内容,如果未能解决你的问题,请参考以下文章
flask 请求处理流程及其源代码分析sessioncookie生成源码
flask Flask-Login 插件及继承 UserMixin 类login_user 源码分析session源码分析