1. 程式人生 > >python web開發 Flask+禁用cookies+session

python web開發 Flask+禁用cookies+session

博主最近在學習flask的過程中,使用session進行登入認證的時候遇到了以下場景:

客戶端禁用cookies 的時候如何使用session進行登入驗證?

因為session的大致實現流程為:伺服器端通過cookie獲取sessionid,從而獲取到session,如果cookie被禁用,伺服器端則無法獲取sessionid,從而認為這次一個全新的請求,從而生成新的session。

在java serverlet中可以通過重寫url在伺服器端獲取session,但是在flask中博主沒有發現這一用法,查閱資料的時候發現可以通過重寫flask的session_interface來重寫session的實現機制,自定義獲取sessionid的途徑,進而獲取session

本文將重寫,flask的session模組,模擬通過url獲取sessionid,通過(header,body)等可以以此為據自行擴充套件。

 

my_session.py

 

首先定義一實體類,多繼承dict,和SessionMixi類,並重寫父類 __setitem__、__getitem__、__delitem__方法。

import uuid
import json
from flask.sessions import SessionMixin, SessionInterface
from itsdangerous import Signer, BadSignature, want_bytes

class MySession(dict, SessionMixin):

    def __init__(self, initial=None, sid=None):
        self.sid = sid
        self.initial = initial
        super(MySession, self).__init__(initial or ())

    def __setitem__(self, key, value):
        super(MySession, self).__setitem__(key, value)

    def __getitem__(self, item):
        return super(MySession, self).__getitem__(item)

    def __delitem__(self, key):
        super(MySession, self).__delitem__(key)


然後定義一實體類,繼承SessionInterface類,重寫其open_session方法(用來建立session)、save_session方法(用來儲存和下發session)程式碼如下:

class MySessionInterface(SessionInterface):
    session_class = MySession
    container = {}

    def __init__(self):
        import redis
        self.redis = redis.Redis(host='10.79.148.226')

    def _generate_sid(self):
        return str(uuid.uuid4().hex)

    def _get_signer(self, app):
        if not app.secret_key:
            return None
        return Signer(app.secret_key, salt='salt!', key_derivation='hmac')

    def open_session(self, app, request):
        # sid = request.cookies.get(app.session_cookie_name)
        sid = request.args.get('sid')
        if not sid:
            sid = self._generate_sid()
            return self.session_class(sid=sid)

        signer = self._get_signer(app)
        try:
            sid_as_bytes = signer.unsign(sid)
            sid = sid_as_bytes.decode()
        except BadSignature:
            sid = self._generate_sid()
            return self.session_class(sid=sid)
        val = self.container.get(sid)
        if val is not None:
            try:
                data = json.loads(val)
                return self.session_class(data, sid=sid)
            except:
                return self.session_class(sid)
        return self.session_class(sid=sid)

    def save_session(self, app, session, response):
        print('save_session')
        domain = self.get_cookie_domain(app)
        path = self.get_cookie_path(app)
        httponly = self.get_cookie_httponly(app)
        secure = self.get_cookie_secure(app)
        expires = self.get_expiration_time(app, session)

        val = json.dumps(dict(session))
        self.container.setdefault(session.sid, val)

        session_id = self._get_signer(app).sign(want_bytes(session.sid))
        print(session_id)

        response.set_cookie(app.session_cookie_name, session_id,
                            expires=expires, httponly=httponly,
                            domain=domain, path=path, secure=secure)

在產生http請求的時候,伺服器端首先會獲取sessionid,然後通過對sessionid的判斷,在session的儲存空間中獲取or生成session。

參考文件:https://cizixs.com/2017/03/08/flask-insight-session/

flask中取得session的過程如下:

  1. 獲取session簽名演算法
  2. 獲取session值
  3. 從簽名演算法取值

因為flask原始碼中,session的資訊是儲存在客戶端的cookie中,所以需要從cookie中獲取session,然後進行解密。

而本文則將session儲存在伺服器的記憶體中,然後通過獲取sessionid,從而從字典中獲取session。

本文通過request.args.get('sid')即可通過url獲取sessionid,進而取出session。

同樣也可以將sessionid,通過header、body等進行傳遞,只要自行編寫相應獲取sessionid 的方法,並且從session的儲存介質中獲取session即可。

flask中儲存session額過程如下:

  1. 判斷此時session是否為空,如為空刪除對應cookie
  2. 判斷session是否變化,如果沒有變化則直接跳過,如果變化生成變化之後session
  3. 將session資訊進行加密,然後通過客戶端的cookie進行儲存

flask原始碼中session的儲存為,將加密過後的session資訊全部儲存在客戶端的cookie中,一旦客戶端cookie被禁用,伺服器端無論如何都無法獲取session資訊,只能把每一次請求都認為是新的請求然後不斷的生成新的session。

其實現方式與token大致相同。

而本文的實現方式則是通過,在伺服器端開闢儲存空間,然後將sessionid,通過cookie、response、header的方法傳遞給前端,然後前端將獲取的sessionid儲存在localstorage中,從而實現實現了sessionid 的儲存。當再次發起請求的時候,從localstorage將獲取到的sessionid,通過url或者header傳到後端,然後實現了禁用cookie使用session進行認證等功能。

app.py

# -*- coding: utf-8 -*-
from flask import Flask, jsonify, session, make_response, request
from werkzeug.wrappers import Response
from my_session import MySessionInterface

app = Flask(__name__)

app.secret_key = 'please-generate-a-random-secret_key!'


app.session_interface = MySessionInterface()

app.config.from_object('setting')


@app.route('/')
def hello_world():
    print(session)
    session['a'] = 'a'
    print(request.cookies)
    print(session)
    return 'hello world'

if __name__ == '__main__':
    app.run()

只要將app.session_interface 指定為自定的SessionInterface即可。

 

因為可以通過重寫session的實現方法,從而控制session的獲取與儲存,我們也可以將session序列化之後儲存在redis、memcached中,比如在使用Nginx做負載均衡的時候,就可以將多臺伺服器的session指定到同一個session儲存的介質中,從而解決負載均衡時session認證失敗的問題。