前言

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. Python基础——爬虫以及简单的数据分析

    目标:使用Python编写爬虫,获取链家青岛站的房产信息,然后对爬取的房产信息进行分析. 环境:win10+python3.8+pycharm Python库: import requests imp ...

  2. @loj - 3046@「ZJOI2019」语言

    目录 @description@ @solution@ @accepted code@ @details@ @description@ 九条可怜是一个喜欢规律的女孩子.按照规律,第二题应该是一道和数据 ...

  3. 给博客使用Butterfly主题并部署到GitHub服务器

    目录 前言 一.安装Butterfly主题 二.将本地博客部署到GitHub服务器 三.将个人域名与GitHub绑定 前言 安装完Hexo框架后,自带的主题在thems文件夹下可以查看,应用后界面: ...

  4. Largest Allowed Area【模拟+二分】

    Largest Allowed Area 题目链接(点击) 题目描述 A company is looking for land to build its headquarters. It has a ...

  5. Java 异常处理的十个建议

    前言 Java异常处理的十个建议,希望对大家有帮助~ 本文已上传github: https://github.com/whx123/JavaHome 公众号:捡田螺的小男孩 一.尽量不要使用e.pri ...

  6. TensorFlow从0到1之TensorFlow多层感知机实现MINIST分类(22)

    TensorFlow 支持自动求导,可以使用 TensorFlow 优化器来计算和使用梯度.它使用梯度自动更新用变量定义的张量.本节将使用 TensorFlow 优化器来训练网络. 前面章节中,我们定 ...

  7. Zookeeper——分布式一致性协议及Zookeeper Leader选举原理

    文章目录 一.引言 二.从ACID到CAP/BASE 三.分布式一致性协议 1. 2PC和3PC 2PC 发起事务请求 事务提交/回滚 3PC canCommit preCommit doCommit ...

  8. c++ UDP套接字服务器端代码示范

    c++ UDP套接字服务器端代码示范 #include<winsock2.h> //包含头文件 #include<stdio.h> #include<windows.h& ...

  9. c++无法解析的外部符号 "int const bufferSize

    无法解析的外部符号 "int const bufferSize 严重性 代码 说明 项目 文件 行 禁止显示状态错误 LNK2001 无法解析的外部符号 "int const bu ...

  10. 一对多分页的SQL到底应该怎么写?

    1. 前言 MySQL一对多的数据分页是非常常见的需求,比如我们要查询商品和商品的图片信息.但是很多人会在这里遇到分页的误区,得到不正确的结果.今天就来分析并解决这个问题. 2. 问题分析 我们先创建 ...