flask 请求处理流程及其源代码分析sessioncookie生成源码
Posted 胖虎是只mao
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了flask 请求处理流程及其源代码分析sessioncookie生成源码相关的知识,希望对你有一定的参考价值。
浏览器首次访问网站的注册页面时,request.cookies 里没有 session ,val 的值就是 None 。这时候执行第 345 行返回 self.session_class
的调用,session_class 属性就是当前模块中定义的 SecureCookieSession
类,最后请求上下文对象的 session 属性值就是此类的实例。
这个实例是类字典对象,现阶段我们正在处理请求,它里面没有键值对,是空的。下一步就是调用视图函数了:
如上图所示,这里涉及到表单类的实例化。
在创建表单类时,其父类是 flask_wtf.form
模块中的 FlaskForm 类,FlaskForm 的父类是 wtforms.form 模块中的 Form 类,这个 Form 类初始化代码如下:
这里重点要提的是上图所示绿色框中的 for 循环代码。其中 self 就是表单类,self._fields
是表单字段的列表。这个列表里有什么呢?显然肯定是有我们在 RegisterFrom 中定义的 username 、email 、password 等字段。此外还有一个 csrf_token
字段,这是重要的一点。
这个 csrf_token 字段是哪里来的呢?我们看下 Form 类的父类 BaseForm ,它也是在 wtforms.form 模块中
如上图所示,第 38 行定义了 _fields
属性,属性值是一个类字典对象。
第 41 行的 fields 变量是一个列表,列表里面的元素是元组,也就是字段名和字段对象合成的元组。
第 44 行定义了 extra_fields
列表,这个列表被添加了一个元素,这个元素是一个元组:
('csrf_token', <UnboundField(CSRFTokenField, (), 'label': 'CSRF Token', 'csrf_impl': <flask_wtf.csrf._FlaskFormCSRF object at 0x7ffff0f5da20>)>)
第 49 行的 for 循环将这些自定义字段和 csrf_token
字段一起加到 self._fields
字典里。
还记得 templates/register.html 中的 form 标签里有一个隐藏域吗?下图所示第 9 行:
这里的 form.csrf_token
其实就是一个字符串,它的值来自上述逻辑中的 CSRFTokenField
类的实例,该实例会根据实际情况提供一个类似下面这样的 input 标签字符串:
<input
id="csrf_token"
name="csrf_token"
type="hidden"
value="IjllNTFlY2QzMGIwMWNkNDliY2I4ZjZjOGE2NTZjZTU0Y2FhZTg2ZmUi.X6Jp1Q.QsgyYTAHBMDm4_hCWGmxbdmDtq4"
/>
如上所示的 input 标签在生成 value 属性值的时候,会调用定义在 flask_wtf.csrf 模块中的 generate_csrf
函数:
如上图所示,关键在最后两行,field_name
变量的值是字符串 'csrf_token'
,显然此时 session 里面是空的,因为是第一次请求嘛。所以会执行第 46 行代码,把 ‘csrf_token’ 作为 key ,哈希值作为 value 加到 session 上下文代理对象中,它也是一个类字典对象。这个哈希值字符串同样会赋值给上面的 input 标签的 value 属性值。
现在我们分析的是浏览器第一次请求时的 session ,再看下视图函数:
第一次请求,上图第 23 行执行完毕后,session 里就有了一组键值对:
'csrf_token': '85b1b19e6fbb91bf580ca27b8345fa30dc7d1e42'
注意,这里的 csrf_token
值与表单隐藏域中的值是一样的,它们两个配套使用就可以防止 CSRF 跨域伪造攻击。
然后跳到上图所示第 27 行。这里又用到了 render_template 方法,此方法定义在 flask.templating 模块中:
如上图所示,我们主要看下第 136 行,它调用了应用对象的 update_template_context 方法:
如上图所示,第 830 行的变量 funcs 是一个列表,列表里面是函数。第 837 行循环调用函数,其中一个是定义在 flask_login.utils 模块中的 _user_context_processor
函数:
如上图所示,第 379 行又调用了该模块下的 _get_user 方法:
如上图所示,第 346 行 current_app 就是应用对象,其 login_manager 属性我们在前面讲过的,是 LoginManager
类的实例。此处调用该实例的 _load_user 方法:
如上图所示,我们现在关心的代码在蓝色框中,if 语句会调用登录管理对象的 _session_protection_failed
方法:
如上图所示,第 339 行设置变量 sess 等于请求上下文对象的 session 属性值,其实也就相当于 session 这个变量。因为是第 1 次请求,第 350 和 351 行的 if 判断都会通过,第 352 行会在 session 中再增加一组键值对。
这样 session 里就有两组键值对了。修改 /handlers/front.py 文件中的 register 视图函数,打印 session 看下:
使用浏览器的隐身模式访问注册页面,终端打印信息如下图所示:
如上图所示,打印三次的结果不同:
- 第一次打印时,session 里是空的;
- 第二次打印时,表单类的创建使得 session 新增了一组 csrf_token 键值对;
- 第三次打印时,
render_template
的执行使得 session 由新增了一组 _fresh 键值对,当然这并不是 Flask 框架增加的,而是 Flask-Login 插件做的事。
现在视图函数调用完了,然后呢?
寻找和调用视图函数的代码在应用对象的 full_dispatch_request
方法中:
如上图所示,第 1938 行的 self.preprocess_request
方法会找到请求对应的视图函数并调用,视图函数的返回值赋值给等号前面的变量 rv 。对于注册页面来说,rv 就是 register.html 生成的前端 HTML 文件字符串。
上图所示最后一行将 rv 作为参数调用了应用对象的 finalize_request
方法:
如上图所示,第 1958 行调用 make_response
方法生成响应对象,这个方法我们会在后面的实验中介绍它的源码。第 1960 行将响应对象作为参数调用 process_response
方法:
如上图所示,前两行的变量 ctx 和 bp 大家学到这个阶段应该已经很清楚了,分别是请求上下文对象和蓝图对象,即 RequestContext
类的实例和 Blueprint 类的实例。接下来 2251 行的 funcs 是一个列表,列表里面是函数,一些在处理完视图函数后要执行的函数。再后面这些代码就是调用这些函数。
我们要讨论的重点是最后三行。第 2258 和 2259 行的注释是我加的,ctx.session
是什么?就是四大全局代理对象之一的 session 。
第 2260 行有一个判断,这个判断通常都会通过,进而执行第 2261 行代码,这里调用的是 SecureCookieSessionInterface 类的 save_session
方法,参数是应用对象、session 和响应对象这三个。我们看下 save_session
方法的源码:
如上图所示,save_session
方法的前半部分,354 和 355 行调用自身的方法获得两个变量的值,domain 和 path 分别是 None 和 ‘/’ 。然后我们看下后半部分:
如上图所示,前面几行变量后面会介绍,这里只提一下第 378 行。self.get_signing_serializer
方法的返回值是一个令牌生成器,要创建令牌生成器就需要用到应用对象的 SECRET_KEY
属性,这个属性通常会定义在配置文件中,它的作用就是在一些重要的节点作为参数创建令牌生成器。调用令牌生成器的 dump 方法将字典化的 session 作为参数生成一个令牌,也叫「加密签名」。
第 379 行调用了响应对象的 set_cookie
方法。此方法用于向响应对象的 headers
属性,也就是响应头中添加键值对。我们看下此方法的源码,在 werkzeug.wrappers.base_response
模块中
如上图所示,此方法的名字已经标识出它的作用,就是给浏览器加 Cookies
的,Cookies 由键值对组成,此方法会向 Cookies 中添加一组键值对。我们看下这些参数:
- key :
app.session_cookie_name
,默认值是 ‘session’ ; - value :这是一个比较复杂的加密哈希值,其中可能包含了重要的信息;
- max_age & expires :键值对的有效期和过期时间,通常设置一个就可以了;
- path :键值对的有效路径;
- domain :键值对的有效域名;
- httponly :这是为防止 XSS 网络攻击的一种设置,将其设置为 True(也就是默认值)可以保护 Cookies 被 JS 脚本盗取;
- samesite :这也是安全机制上的一种设置,用来防止 CSRF 攻击和用户追踪。
我们看下 set_cookie
方法的源码:
如上图所示,这里就是调用 headers 的 add 方法向响应头中添加一个元素,这个元素是一个元组,第一项是字符串 “Set-Cookie” ,第二项是 dump_cookie
根据参数处理得到的一个字符串。注意 headers 不是类字典对象,而是类列表对象。就像下面这样:
[
(xxx, xxx),
(xxx, xxx),
('Set-Cookie', 'xxx')
]
所以,headers 里面可以有多个 Set-Cookie
。
好,现在我们已经处理了第一次请求,这个请求是浏览器首次向服务器发起的 /register 请求。我们已经处理了请求并将一个名为 ‘session’ 的 Cookie 传回浏览器,此时浏览器里的 Cookies 应该是这样的:
浏览器收到服务器的响应后,从响应头中取出 "Set-Cookie"
对应的值,将其添加到 Cookies 中,下次向服务器发送请求的时候会自动携带 Cookies 中的内容。
我们只需要知道 session 是服务器中的一个全局变量,它是类字典对象,其中存储的键值对可能会在响应对象创建之后以加密签名的形式添加到响应头的 Set-Cookie
字段中。浏览器收到响应后,将 Set-Cookie 字段中的数据添加到 Cookies 里面,再次发送请求时会携带 Cookies 。如果其中有能够证明用户身份的数据,就会被服务器识别。
以上是关于flask 请求处理流程及其源代码分析sessioncookie生成源码的主要内容,如果未能解决你的问题,请参考以下文章