前言

flask_session是flask框架实现session功能的一个插件,用来替代flask自带的session实现机制,flask默认的session信息保存在cookie中,不够安全和灵活。

flask的session机制

session是用来干什么的呢?由于http协议是一个无状态的协议,也就是说同一个用户第一次请求和第二次请求是完全没有关系的,但是现在的网站基本上有登录使用的功能,这就要求必须实现有状态,而session机制实现的就是这个功能。

实现的原理:

用户第一次请求后,将产生的状态信息保存在session中,这时可以把session当做一个容器,它保存了正在使用的所有用户的状态信息;这段状态信息分配了一个唯一的标识符用来标识用户的身份,将其保存在响应对象的cookie中;当第二次请求时,解析cookie中的标识符,拿到标识符后去session找到对应的用户的信息。

简单使用

from flask import Flask,session
app = Flask(__name__)
@app.route('/test1/')
def test():
session.setdefault('name', 'xiaoming')
return 'OK'
if __name__ == '__main__':
app.run(host='127.0.0.1', port=80, debug=True)

在flask中,如果我们想要获取session信息,直接通过flask的session获取就可以了,这是因为session是一个代理对象,代理当前请求上下文的session属性。

session源码分析

依据上述session的原理,来分析一下flask框架的session机制实现的过程。

Flask对象使用open_session方法和save_session方法打开和保存会话信息,请求在创建请求上下文后会调用open_session方法获取用户的信息,在执行完处理逻辑后会调用save_session方法保存用户的信息。

open_session和save_session

def open_session(self, request):
# 调用了app的session_interface对象的方法
return self.session_interface.open_session(self, request) def save_session(self, session, response):
return self.session_interface.save_session(self, session, response)

app对象默认的session_interface = SecureCookieSessionInterface(),SecureCookieSessionInterface重写了SessionInterface对象的open_session方法和save_session方法。

class SecureCookieSessionInterface(SessionInterface):
pass def open_session(self, app, request):
# 检测是否设置了secret_key参数,返回一个签名对象
s = self.get_signing_serializer(app)
if s is None:
return None
# 去cookie中获取session信息
val = request.cookies.get(app.session_cookie_name)
# 如果是第一次请求,返回一个空的SecureCookieSession对象,会被交给请求上下文的session属性管理
if not val:
return self.session_class()
# 获取session的失效时间
max_age = total_seconds(app.permanent_session_lifetime)
try:
# 对session信息进行解码得到用户信息
data = s.loads(val, max_age=max_age)
# 返回有用户信息的session对象
return self.session_class(data)
except BadSignature:
return self.session_class() def save_session(self, app, session, response):
# 获取cookie设置的域
domain = self.get_cookie_domain(app)
# 获取cookie设置的路径
path = self.get_cookie_path(app)
... # 检测SESSION_REFRESH_EACH_REQUEST参数配置
if not self.should_set_cookie(app, session):
return
# 返回SESSION_COOKIE_HTTPONLY参数配置
httponly = self.get_cookie_httponly(app)
# 返回SESSION_COOKIE_SECURE参数配置
secure = self.get_cookie_secure(app)
# 返回失效的时间点
expires = self.get_expiration_time(app, session)
#将用户的数据加密
val = self.get_signing_serializer(app).dumps(dict(session))
# 设置cookie
response.set_cookie(app.session_cookie_name, val,
expires=expires, httponly=httponly,
domain=domain, path=path, secure=secure)

请求上下文RequestContext的session属性是一个SecureCookieSession对象,可以将其看做一个字典;

相关的配置参数

SESSION_COOKIE_NAME:设置返回给客户端的cookie的名称,默认是“session”;放置在response的头部;

SESSION_COOKIE_DOMAIN:设置会话的域,默认是当前的服务器,因为Session是一个全局的变量,可能应用在多个app中;

SESSION_COOKIE_PATH:设置会话的路径,即哪些路由下应该设置cookie,如果不设置,那么默认为‘/’,所有的路由都会设置cookie;这个参数和SESSION_COOKIE_DOMAIN是互斥的

SERVER_NAME:设置服务器的名字,一般不用;

SESSION_COOKIE_SECURE:如果 cookie 标记为“ secure ”,那么浏览器只会使用基于 HTTPS 的请求发 送 cookie,应用必须使用 HTTPS 服务来启用本变量,默认False

APPLICATION_ROOT:设置应用的根路径;

SESSION_REFRESH_EACH_REQUEST:是否应该为每一个请求设置cookie,默认为True,如果为False则必须显性调用set_cookie函数;

SESSION_COOKIE_HTTPONLY:cookie应该和httponly标志一起设置,默认为True,这个一般采用默认。

PERMANENT_SESSION_LIFETIME:设置session的有效期,即cookie的失效时间,单位是s。这个参数很重要,因为默认会话是永久性的。

SESSION_COOKIE_HTTPONLY:默认为true,表示允许js脚本访问cookie;

小结

flask默认通过SecureCookieSessionInterface对象管理session,其重写了SessionInterface对象的open_session方法和save_session方法,将用户的数据加密后存储在cookie中。

自定义session存储

通过分析flask的session实现机制,一般认为将session信息放在cookie中不够保险,那么我们可以实现自己的session机制,思路是创建一个类继承SessionInterface,然后重写open_session方法和save_session方法,再使用我们的类替换app的session_interface属性即可。

比如我要将session信息保存在一个session.json中。

第一步:设置必要的配置参数
# 配置session存放的路径
MY_SESSION_PATH = '\session.json'
'SESSION_TYPE' = 'file'
# 配置默认的seesion的配置参数
SECRET_KEY = ''
SESSION_USE_SIGNER = True
# session的有效期,单位:秒
PERMANENT_SESSION_LIFETIME = 7200
第二步:创建自己的SessionInterface的子类
from flask.sessions import *
try:
import cPickle as pickle
except ImportError:
import pickle import json
from uuid import uuid4
import time # 我们需要自定义一个Session对象用来存储用户的信息,它使用一个唯一的id标识,模仿SecureCookieSession的实现方法
class SecureFileSession(CallbackDict, SessionMixin):
def __init__(self, initial=None, sid=None, permanent=None):
def on_update(self):
self.modified = True
CallbackDict.__init__(self, initial, on_update)
self.sid = sid # session的标识
if permanent:
self.permanent = permanent # 失效时间
self.modified = False # 我们使用uuid作为签名,省略校验过程
class NewSessionInterface(SessionInterface): def _generate_sid(self):
return str(uuid4()) class JsonFileSessionInterface(NewSessionInterface):
# 用来序列化的包
serializer = pickle
session_class = SecureFileSession def __init__(self, app=None):
self.app = app
if app is not None:
self.init_app(app) def init_app(self, app):
"""
替换app的session_interface属性
:param app:
:return:
"""
app.session_interface = self._get_interface(app) def _get_interface(self, app):
"""
加载配置参数返回本身,必须配置'SESSION_TYPE'和'MY_SESSION_PATH'参数,否则使用默认的session
:param app:
:return:
"""
config = app.config.copy()
if config['SESSION_TYPE'] == 'file':
if not config['MY_SESSION_PATH']:
return SecureCookieSessionInterface()
self.path = app.static_folder + config['MY_SESSION_PATH'] # session文件路径
self.permanent = total_seconds(app.permanent_session_lifetime) # 失效时间
return self
return SecureCookieSessionInterface() def open_session(self, app, request):
"""
从文件中获取session数据
:param app:
:param request:
:return:
"""
# 获取session签名
sid = request.cookies.get(app.session_cookie_name)
permanent = int(time.time()) + self.permanent
# 如果没有说明是第一次访问,返回空session对象
if not sid:
# 获取一个uuid
sid = self._generate_sid()
return self.session_class(sid=sid, permanent=permanent) with open(self.path, 'r', encoding='utf-8') as f:
v = f.read()
# 如果session为空,返回空session对象
if not v:
return self.session_class(sid=sid, permanent=permanent)
try:
val = json.loads(v)
except ValueError as e:
print('配置参数错误:{}'.format(e))
return self.session_class(sid=sid, permanent=permanent)
else:
self.val = val
# 通过sid获取信息
data = val.get(sid)
if not data:
return self.session_class(sid=sid, permanent=permanent)
# 判断以前的信息是否超时
if permanent - int(data['permanent']) > self.permanent:
return self.session_class(sid=sid, permanent=permanent)
return self.session_class(data, sid=sid) def save_session(self, app, session, response):
"""
保存session信息
:param app:
:param session:
:param response:
:return:
"""
# 前面借鉴flask默认的实现方式
domain = self.get_cookie_domain(app)
path = self.get_cookie_path(app)
if not session:
if session.modified:
response.delete_cookie(app.session_cookie_name,
domain=domain, path=path)
return
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) # 将session信息保存在文件中
session.update({'permanent': int(time.time()) + self.permanent})
if hasattr(self, 'val') and isinstance(self.val, dict):
self.val.update({session.sid: dict(session)})
else:
self.val = {session.sid: dict(session)} with open(self.path, 'w', encoding='utf-8') as f:
result = json.dumps(self.val)
f.write(result)
response.set_cookie(app.session_cookie_name, session.sid,
expires=expires, httponly=httponly,
domain=domain, path=path, secure=secure)
第三步:初始化替换app的session_interface
app = Flask(__name__,template_folder='static/html')
app.config.update({
'SECRET_KEY':'',
'SESSION_USE_SIGNER':True,
'SESSION_TYPE':'file',
'MY_SESSION_PATH':'\session.json'})
from session_file import JsonFileSessionInterface
se = JsonFileSessionInterface(app=app)
if __name__ == '__main__':
app.run(host='127.0.0.1', port=80, debug=True)

小结

经过上面的三步,我们就可以将自己实现的session对象运用到flask项目中,我们采用的是文件存储session,实际项目中有redis,memcached,mysql等都可以存储session,将它们整合起来,于是flask_session插件就应运而生了。

flask_session扩展

flask_session插件就是官方推荐的session实现插件,整合了redis,memcached,mysql,file,mongodb等多种第三方存储session信息,它的实现原理就是我上面自定义session所做的工作。

安装

pip install Flask-Session

配置参数详解

flask_session初始化后,会从app的配置中读取参数,比较重要的有:

设置session保存的位置,可以有多种配置,
SESSION_TYPE = ‘null’ : 采用flask默认的保存在cookie中;
SESSION_TYPE = ‘redis’ : 保存在redis中
SESSION_TYPE = ‘memcached’ : 保存在memcache
SESSION_TYPE = 'filesystem' : 保存在文件
SESSION_TYPE = 'mongodb' : 保存在MongoDB
SESSION_TYPE = 'sqlalchemy' : 保存在关系型数据库 SESSION_KEY_PREFIX = 'session:' :session存储时的键的前缀
SESSION_USE_SIGNER:是否为cookie设置签名来保护数据不被更改,默认是False;如果设置True,那么必须设置flask的secret_key参数;
SESSION_PERMANENT:是否使用永久会话,默认True,但是如果设置了PERMANENT_SESSION_LIFETIME,则这个失效; SESSION_REDIS:
如果SESSION_TYPE = ‘redis’,那么设置该参数连接哪个redis,其是一个连接对象;如果不设置的话,默认连接127.0.0.1:6379/0
for example:
SESSION_REDIS = redis.StrictRedis(host="127.0.0.1", port=6390, db=4)

关于其他的保存中间人参考:https://pythonhosted.org/Flask-Session/

一份常用的flask_session的配置

# 指明对session数据进行保护
SECRET_KEY = ''
SESSION_USE_SIGNER = True
# 指明保存到redis中
SESSION_TYPE = "redis"
SESSION_REDIS = redis.StrictRedis(host="127.0.0.1", port=6390, db=4)
# session的有效期,单位:秒
PERMANENT_SESSION_LIFETIME = 7200

flask_session的使用流程

 # extensions.py
# 创建一个session对象
from flask_session import Session
# 创建一个Session的实例
session = Session() # 在app初始化时初始化session对象,即加载配置
# __init__.py
from flask import Flask
app = Flask(__name__)
session.init_app(app=app) # task.py
from Flask import session @app.route('/test', methods=['POST'])
def test():
session.get('user',None)
return ""

flask 源码专题(六):session处理机制的更多相关文章

  1. flask 源码专题(五):SqlAlchemy 中操作数据库时session和scoped_session的区别

    1原生session: from sqlalchemy.orm import sessionmaker from sqlalchemy import create_engine from sqlalc ...

  2. flask 源码专题(二):请求上下文与全文上下文

    源码解析 0. 请求入口 if __name__ == '__main__': app.run() def run(self, host=None, port=None, debug=None, lo ...

  3. flask 源码专题(十一):LocalStack和Local对象实现栈的管理

    目录 04 LocalStack和Local对象实现栈的管理 1.源码入口 1. flask源码关于local的实现 2. flask源码关于localstack的实现 3. 总结 04 LocalS ...

  4. flask源码解析之session

    内容回顾 cookie与session的区别: 1. session 是保存在服务端的键值对 2. cookie 只能保存4096个字节的数据,但是session不受限制 3. cookie保存在浏览 ...

  5. flask 源码专题(九):flask扩展点

    1. 信号(源码) 信号,是在flask框架中为我们预留的钩子,让我们可以进行一些自定义操作. pip3 install blinker 2. 根据flask项目的请求流程来进行设置扩展点 中间件 # ...

  6. flask 源码专题(十):flash源码研究

    flash源码 def flash(message, category="message"): flashes = session.get("_flashes" ...

  7. flask 源码专题(八):路由加载

    1.示例代码 from flask import Flask app = Flask(__name__,static_url_path='/xx') @app.route('/index') def ...

  8. flask 源码专题(一):app.run()的背后

    当我们用Flask写好一个app后, 运行app.run()表示监听指定的端口, 对收到的request运行app生成response并返回. 现在分析一下, 运行app.run()后具体发生了什么事 ...

  9. flask 源码专题(三):请求上下文和应用上下文入栈与出栈

    1.请求上下文和应用上下文入栈 # 将ctx入栈,但是内部也将应用上下文入栈 ctx.push() def push(self): # 获取到的 top == ctx top = _request_c ...

随机推荐

  1. 不需重新编译php,安装postgresql扩展(pgsql和pdo_pgsql)

    为了不重新编译php,使用phpize工具进行追加. 1.下载phpX安装包 访问php官方下载页,找到自己对应的php版本:https://secure.php.net/downloads.php ...

  2. WAMP3.1 安装php_redis.dll扩展并配置php.ini

    一. 下载对应版本的php_redis.dll 下载地址:http://windows.php.net/downloads/pecl/releases/redis 注:php7目录下有php7.dll ...

  3. 658.找到K个最接近的元素

    2020-03-10 找到 K 个最接近的元素 给定一个排序好的数组,两个整数 k 和 x,从数组中找到最靠近 x(两数之 差最小)的 k 个数.返回的结果必须要是按升序排好的.如果有两个数与 x 的 ...

  4. Floating-Point Hazard【求导公式】

    Floating-Point Hazard 题目链接(点击) 题目描述 Given the value of low, high you will have to find the value of ...

  5. 在CentOS8下利用seafile软件搭建私有云

    一.安装前准备工作 1. 安装EPEL源 EPEL (Extra Packages for Enterprise Linux)是基于Fedora的一个项目,为“红帽系”的操作系统提供额外的软件包,适用 ...

  6. Asp.Net Mvc 控制器详解

    理解控制器 控制器的角色 (1)中转作用:控制器通过前面的学习大家应该知道它是一个承上启下的作用,根据用户输入,执行响应行为(动 作方法),同时在行为中调用模型的业务逻辑,返回给用户结果(视图). ( ...

  7. Dubbo——服务引用

    文章目录 引言 正文 服务订阅 Invoker的创建 单注册中心的Invoker创建 Dubbo直连的Invoker创建 创建代理类 引言 上一篇我们分析了服务发布的原理,可以看到默认是创建了一个Ne ...

  8. Vue错误汇总

    1.Vue导入js后没反应{{msg}}仍是{{msg}} 错误原因:js里写错了,或者没new一个Vue html页面: 解决方法:代码加入 new

  9. 一分钟开始持续集成之旅系列之:C 语言 + Makefile

    作者:CODING - 朱增辉 前言 make 工具非常强大,配合 makefile 文件可以实现软件的自动化构建,但是执行 make 命令依然需要经历手动输入执行.等待编译完成.将目标文件转移到合适 ...

  10. Arthas协助排查线上skywalking不可用问题

    前言 首先描述下问题的背景,博主有个习惯,每天上下班的时候看下skywalking的trace页面的error情况.但是某天突然发现生产环境skywalking页面没有任何数据了,页面也没有显示任何的 ...