flask基礎之session原理詳解(十)
前言
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 = '123'
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':'123',
'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 = '123'
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 ""
參考:
https://pythonhosted.org/Flask-Session/
https://dormousehole.readthedocs.io/en/latest