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 ""


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM