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。
flask中取得session的過程如下:
- 獲取session簽名演算法
- 獲取session值
- 從簽名演算法取值
因為flask原始碼中,session的資訊是儲存在客戶端的cookie中,所以需要從cookie中獲取session,然後進行解密。
而本文則將session儲存在伺服器的記憶體中,然後通過獲取sessionid,從而從字典中獲取session。
本文通過request.args.get('sid')即可通過url獲取sessionid,進而取出session。
同樣也可以將sessionid,通過header、body等進行傳遞,只要自行編寫相應獲取sessionid 的方法,並且從session的儲存介質中獲取session即可。
flask中儲存session額過程如下:
- 判斷此時session是否為空,如為空刪除對應cookie
- 判斷session是否變化,如果沒有變化則直接跳過,如果變化生成變化之後session
- 將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認證失敗的問題。