flask 源码解析:session
这是 flask 源码解析系列文章的其中一篇,本系列所有文章列表:
session 简介在解析 session 的实现之前,我们先介绍一下 session 怎么使用。session 可以看做是在不同的请求之间保存数据的方法,因为 HTTP 是无状态的协议,但是在业务应用上我们希望知道不同请求是否是同一个人发起的。比如购物网站在用户点击进入购物车的时候,服务器需要知道是哪个用户执行了这个操作。 在 flask 中使用 session 也很简单,只要使用? from?flask?import?Flask,?session,?escape,?request app?=?Flask(__name__)app.secret_key?=?'please-generate-a-random-secret_key'@app.route("/")def?index():????if?'username'?in?session:????????return?'hello,?{}'.format(escape(session['username'])) ????return?'hello,?stranger'@app.route("/login",?methods=['POST'])def?login(): ????session['username']?=?request.form['username'] ????return?'login?success'if?__name__?==?'__main__': ????app.run(host='0.0.0.0',?port=5000,?debug=True) 上面这段代码模拟了一个非常简单的登陆逻辑,用户访问? 直接访问的话,我们可以看到返回? ???~?http?-v?http://127.0.0.1:5000/ GET?/?HTTP/1.1 Accept:?*/* Accept-Encoding:?gzip,?deflate Host:?127.0.0.1:5000 User-Agent:?HTTPie/0.8.0 HTTP/1.0?200?OK Content-Length:?14 Content-Type:?text/html;?charset=utf-8 Date:?Wed,?01?Mar?2017?04:22:18?GMT Server:?Werkzeug/0.11.2?Python/2.7.10 hello?stranger 然后我们模拟登陆请求, ???~?http?-v?-f?--session=mysession?POST?http://127.0.0.1:5000/login?username=cizixs POST?/login?HTTP/1.1 Accept:?*/* Accept-Encoding:?gzip,?deflate Content-Length:?15 Content-Type:?application/x-www-form-urlencoded;?charset=utf-8 Host:?127.0.0.1:5000 User-Agent:?HTTPie/0.8.0 username=cizixs HTTP/1.0?200?OK Content-Length:?13 Content-Type:?text/html;?charset=utf-8 Date:?Wed,?01?Mar?2017?04:20:54?GMT Server:?Werkzeug/0.11.2?Python/2.7.10 Set-Cookie:?session=eyJ1c2VybmFtZSI6ImNpeml4cyJ9.C5fdpg.fqm3FTv0kYE2TuOyGF1mx2RuYQ4;?HttpOnly;?Path=/ login?success 最重要的是我们看到 response 中有? 继续,这个时候我们用? ???~?http?-v?--session=mysession?http://127.0.0.1:5000/ GET?/?HTTP/1.1 Accept:?*/* Accept-Encoding:?gzip,?deflate Cookie:?session=eyJ1c2VybmFtZSI6ImNpeml4cyJ9.C5fevg.LE03yEZDWTUMQW-nNkTr1zBEhKk Host:?127.0.0.1:5000 User-Agent:?HTTPie/0.8.0 HTTP/1.0?200?OK Content-Length:?11 Content-Type:?text/html;?charset=utf-8 Date:?Wed,?01?Mar?2017?04:25:46?GMT Server:?Werkzeug/0.11.2?Python/2.7.10 Set-Cookie:?session=eyJ1c2VybmFtZSI6ImNpeml4cyJ9.C5feyg.sfFCDIqfef4i8cvxUClUUGQNcHA;?HttpOnly;?Path=/ hellocizixs 这次注意在发送的请求中,客户端带了? 总结一下:session 是通过在客户端设置 cookie 实现的,每次客户端发送请求的时候会附带着所有的 cookie,而里面保存着一些重要的信息(比如这里的用户信息),这样服务器端就能知道客户端的信息,然后根据这些数据做出对应的判断,就好像不同请求之间是有记忆的。 解析我们知道 session 是怎么回事了,这部分就分析一下 flask 是怎么实现它的。 请求过程不难想象,session 的大致解析过程是这样的:
注意:session 和 cookie 的转化过程中,应该考虑到安全性,不然直接使用伪造的 cookie 会是个很大的安全隐患。 在?flask 上下文那篇文章中,我们知道,每次请求过来的时候,我们访问的? self.session?=?self.app.open_session(self.request)if?self.session?is?None: ????self.session?=?self.app.make_null_session() 它初始化了? 我们来看看? def?open_session(self,?request): ????return?self.session_interface.open_session(self,?request) 在? session_interface?=?SecureCookieSessionInterface() 后面遇到 session 有关方法解释,我们会直接讲解? null_session_class?=?NullSessiondef?make_null_session(self,?app): ????return?self.null_session_class()def?open_session(self,?app,?request): ????#?获取?session?签名的算法 ????s?=?self.get_signing_serializer(app) ????if?s?is?None: ????????return?None ????#?从?cookie?中获取?session?变量的值 ????val?=?request.cookies.get(app.session_cookie_name) ????if?not?val: ????????return?self.session_class() ????#?因为?cookie?的数据需要验证是否有篡改,所以需要签名算法来读取里面的值 ????max_age?=?total_seconds(app.permanent_session_lifetime) ????try: ????????data?=?s.loads(val,?max_age=max_age) ????????return?self.session_class(data) ????except?BadSignature: ????????return?self.session_class()
这里有两点需要特殊说明的:签名算法是怎么工作的?session 对象到底是怎么定义的? session 对象默认的 session 对象是? class?SessionMixin(object): ????def?_get_permanent(self): ????????return?self.get('_permanent',?False) ????def?_set_permanent(self,?value): ????????self['_permanent']?=?bool(value) ????#:?this?reflects?the?``'_permanent'``?key?in?the?dict. ????permanent?=?property(_get_permanent,?_set_permanent) ????del?_get_permanent,?_set_permanent ????modified?=?Trueclass?SecureCookieSession(CallbackDict,?SessionMixin): ????"""Base?class?for?sessions?based?on?signed?cookies.""" ????def?__init__(self,?initial=None): ????????def?on_update(self): ????????????self.modified?=?True ????????CallbackDict.__init__(self,?initial,?on_update) ????????self.modified?=?False 怎么知道实例的数据被更新过呢?? NOTE: 对于开发者来说,可以把? 签名算法都获取 cookie 数据的过程中,最核心的几句话是: s?=?self.get_signing_serializer(app)val?=?request.cookies.get(app.session_cookie_name)data?=?s.loads(val,?max_age=max_age)return?self.session_class(data) 其中两句都和? 我们继续看? def?get_signing_serializer(self,?app):????if?not?app.secret_key: ????????return?None ????signer_kwargs?=?dict( ????????key_derivation=self.key_derivation,????????digest_method=self.digest_method ????)????return?URLSafeTimedSerializer(app.secret_key,????????salt=self.salt,????????serializer=self.serializer,????????signer_kwargs=signer_kwargs) 我们看到这里需要用到很多参数:
应答过程 flask 会在请求过来的时候自动解析 cookie 的值,把它变成? 之前的文章讲解了应答的过程,其中? def?process_response(self,?response): ????... ????if?not?self.session_interface.is_null_session(ctx.session): ????????self.save_session(ctx.session,?response) ????return?response 这里就是 session 在应答中出现的地方,思路也很简单,如果需要就调用? def?save_session(self,?response): ????????domain?=?self.get_cookie_domain(app) ????????path?=?self.get_cookie_path(app) ????????#?如果?session?变成了空字典,flask?会直接删除对应的?cookie ????????if?not?session: ????????????if?session.modified: ????????????????response.delete_cookie(app.session_cookie_name,???????????????????????????????????????domain=domain,?path=path) ????????????return ????????#?是否需要设置?cookie。如果?session?发生了变化,就一定要更新?cookie,否则用户可以?`SESSION_REFRESH_EACH_REQUEST`?变量控制是否要设置?cookie ????????if?not?self.should_set_cookie(app,?session): ????????????return ????????httponly?=?self.get_cookie_httponly(app) ????????secure?=?self.get_cookie_secure(app) ????????expires?=?self.get_expiration_time(app,?session) ????????val?=?self.get_signing_serializer(app).dumps(dict(session)) ????????response.set_cookie(app.session_cookie_name,?val,????????????????????????????expires=expires,????????????????????????????httponly=httponly,????????????????????????????domain=domain,?path=path,?secure=secure) 这段代码也很容易理解,就是从? 解密 session有时候在开发或者调试的过程中,需要了解 cookie 中保存的到底是什么值,可以通过手动解析它的值。 前面两部分的内容可以通过下面的方式获取,代码也可直观,就不给出解释了: In?[1]:?from?itsdangerous?import?* In?[2]:?s?=?'eyJ1c2VybmFtZSI6ImNpeml4cyJ9.C5fdpg.fqm3FTv0kYE2TuOyGF1mx2RuYQ4'In?[3]:?data,?timstamp,?secret?=?s.split('.')In?[4]:?base64_decode(data)Out[4]:?'{"username":"cizixs"}'In?[5]:?bytes_to_int(base64_decode(timstamp))Out[5]:?194502054 In?[7]:?time.strftime('%Y-%m-%d?%H:%I%S',?time.localtime(194502054+EPOCH))Out[7]:?'2017-03-01?12:1254' 总结flask 默认提供的 session 功能还是很简单的,满足了基本的功能。但是我们看到 flask 把 session 的数据都保存在客户端的 cookie 中,这里只有用户名还好,如果有一些私密的数据(比如密码,账户余额等等),就会造成严重的安全问题。可以考虑使用?flask-session?这个三方的库,它把数据保存在服务器端(本地文件、redis、memcached),客户端只拿到一个 sessionid。 session 主要是用来在不同的请求之间保存信息,最常见的应用就是登陆功能。虽然直接通过? 参考资料
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |