1、Flask
中session
的實現原理:服務器與客戶端的協作
HTTP 協議是無狀態的——服務器無法區分兩次請求是否來自同一用戶。這意味著,用戶登錄后跳轉到其他頁面時,服務器會“忘記”用戶身份。
為解決這一問題,Web 開發中引入了會話管理(Session Management),根據會話的存儲位置分為 服務器端存儲 和 客戶端存儲。
Flask
的 session
是典型的客戶端存儲會話方案,核心依賴客戶端 Cookie 存儲數據,服務器僅負責生成簽名、驗證數據完整性。以下從服務器角色和客戶端角色兩部分拆解其實現原理。
1.1、服務器角色
1.1.1、生成簽名:響應階段(用戶登錄等場景)
當用戶觸發會話創建(如登錄成功),服務器執行以下步驟:
- 步驟1:數據序列化:將會話數據(如
{"username": "alice", "is_login": True}
)序列化為 JSON 格式({"username":"alice","is_login":True}
)。 - 步驟2:Base64編碼:將 JSON 字符串通過 Base64 編碼為“數據部分”(如
eyJ1c2VybmFtZSI6ImFsaWNlIiwiaXNfbG9naW4iOnRydWV9
)。 - 步驟3:生成HMAC簽名:使用
secret_key
對“數據部分”生成 HMAC 簽名(“簽名部分”,如YlZ4Vg
)。 - 步驟4:組裝Cookie:將“數據部分”與“簽名部分”用
.
連接,形成完整的 Cookie 值(數據部分.簽名部分
)。 - 步驟5:返回客戶端:通過響應頭
Set-Cookie: session=數據部分.簽名部分; ...
將 Cookie 發送給客戶端。
1.1.2、驗證簽名:請求階段(用戶訪問其他頁面)
用戶后續訪問頁面時,客戶端會攜帶 Cookie 發送請求,服務器執行以下驗證:
- 步驟1:提取Cookie:從請求頭中讀取
session
Cookie 的值(數據部分.簽名部分
)。 - 步驟2:拆分數據與簽名:將 Cookie 值按
.
拆分為“數據部分”和“簽名部分”。 - 步驟3:重新計算簽名:使用相同的
secret_key
對“數據部分”重新生成 HMAC 簽名。 - 步驟4:比對簽名:若新生成的簽名與 Cookie 中的“簽名部分”一致,說明數據未被篡改,會話有效;否則標記會話無效(如視為未登錄)。
1.2、客戶端角色
1.2.1、存儲Cookie:接收并持久化
瀏覽器接收到服務器返回的 Set-Cookie
響應頭后,會將會話 Cookie 存儲在本地(如內存或硬盤)。默認情況下,Cookie 是“臨時會話”(關閉瀏覽器后刪除),若通過 session.permanent = True
可設置為長期有效(如31天)。
1.2.2、攜帶Cookie:自動附加請求
每次向同一域名發送請求時,瀏覽器會自動將 session
Cookie 附加到請求頭中(格式:Cookie: session=數據部分.簽名部分
)。這一行為由瀏覽器自動完成,用戶無需手動操作。
1.2.3、無法篡改數據:簽名機制限制
客戶端可以查看 Cookie 中的“數據部分”(Base64 解碼后為明文 JSON),但無法安全篡改數據:
- 若修改“數據部分”(如將
username
改為admin
),需同時偽造匹配的“簽名部分”; - 由于簽名依賴服務器私有的
secret_key
,攻擊者無法生成合法簽名,篡改后的數據會被服務器驗證拒絕。
這一設計使 Flask
session
具備輕量、分布式友好的優勢,但也因客戶端存儲的特性,存在數據大小限制(4KB)和明文存儲敏感信息的風險。實際開發中,需根據場景選擇是否擴展至服務器端存儲(如Flask-Session
結合 Redis)。
2、基礎使用:從初始化到增刪改查
2.1、初始化:設置 secret_key
服務器使用 secret_key
對會話數據生成一個哈希簽名(HMAC),并將會話數據與簽名一起存入 Cookie(格式:數據部分.簽名部分
)。
簽名僅驗證數據完整性,不隱藏數據內容,會話數據在 Cookie 中以明文(Base64 編碼的 JSON)存儲,客戶端可直接解碼查看(如通過瀏覽器開發者工具)。
from flask import Flask, session app = Flask(__name__)
# 必須設置 secret_key(生產環境需使用高強度隨機字符串,如 os.urandom(24) 生成)
app.secret_key = b'_5#y2L"F4Q8z\n\xec]/' # 示例密鑰(實際需替換)
Flask 默認不提供加密,若需隱藏會話數據內容(如存儲敏感信息),需通過擴展庫(如
itsdangerous
或Flask-EncryptedSession
)對數據加密后再簽名。
2.2、常用操作:增、查、刪、清
2.2.1、存儲數據:設置會話變量
通過字典賦值語法,將會話數據存入 session
:
@app.route('/login', methods=['POST'])
def login(): username = request.form.get('username') # 假設用戶驗證通過(如數據庫查詢) session['username'] = username # 存儲用戶名到 session session['is_admin'] = False # 存儲布爾值 return redirect(url_for('profile'))
2.2.2、讀取數據:獲取會話變量
讀取會話數據時,Flask session
支持兩種方式:通過 session.get()
安全獲取,或直接通過 session[key]
取值。
2.2.2.1、session.get(key, default=None)
get()
方法是更推薦的讀取方式,核心優勢是鍵不存在時返回默認值(默認 None
),避免 KeyError
異常導致應用崩潰。
示例:
@app.route('/profile')
def profile(): # 使用 get() 讀取,無 username 鍵時返回 None username = session.get('username') if not username: return redirect(url_for('login')) # 未登錄則跳轉 return f"歡迎,{username}!"
2.2.2.2、session[key]
若明確會話中存在目標鍵,可直接通過 session[key]
取值。
但需注意:鍵不存在時會拋出 KeyError
異常,需配合 try-except
捕獲異常,否則可能導致服務器返回 500
錯誤。
示例(需配合異常處理):
@app.route('/dashboard')
def dashboard(): try: # 直接取值(假設用戶已登錄,username 必然存在) username = session['username'] return f"管理面板 - 歡迎 {username}!" except KeyError: return redirect(url_for('login')) # 未登錄時捕獲異常并跳轉
2.2.3、刪除數據:移除指定會話變量
使用 session.pop(key)
或直接 del session[key]
刪除指定鍵:
@app.route('/logout')
def logout(): session.pop('username') # 移除用戶名 # 或 del session['username'](無該鍵時會拋 KeyError) return redirect(url_for('login'))
2.2.4、清除所有數據:銷毀會話
使用 session.clear()
清空當前會話的所有數據:
@app.route('/reset')
def reset(): session.clear() # 清空會話 return "會話已重置"
3、安全實踐:避免會話攻擊的關鍵配置
3.1、secret_key
:會話安全的“命門”
- 必須保密:
secret_key
泄露會導致攻擊者偽造或篡改會話數據(如生成包含任意username
的 Cookie)。 - 生產環境建議:
- 不要硬編碼在代碼中(通過環境變量或配置文件讀取)。
- 使用至少 32 字節的隨機字符串(如
import os; app.secret_key = os.urandom(32)
)。
3.2、Cookie 安全標志:防御 XSS 與 CSRF
Flask session
本質是一個 Cookie,通過設置以下標志增強安全性:
配置項 | 作用 | 生產環境建議 |
---|---|---|
session.cookie_secure | 僅允許 HTTPS 傳輸 Cookie(防止中間人攻擊竊取 Cookie 明文) | 設為 True (需部署 HTTPS) |
session.cookie_httponly | 禁止 JavaScript 訪問 Cookie(防御 XSS 攻擊讀取會話數據) | 設為 True (默認已開啟) |
session.cookie_samesite | 限制 Cookie 僅在同站點請求中發送(防御 CSRF 攻擊偽造請求攜帶 Cookie) | 設為 'Lax' 或 'Strict' |
配置示例:
app.config.update({ 'SESSION_COOKIE_SECURE': True, # 僅 HTTPS 'SESSION_COOKIE_HTTPONLY': True, # 禁止 JS 訪問 'SESSION_COOKIE_SAMESITE': 'Lax' # 同站點策略
})
3.3、會話有效期:控制用戶“保持登錄”
默認情況下,Flask session
是臨時會話(關閉瀏覽器后失效)。若需長期有效(如“記住我”功能),可通過 session.permanent = True
設置:
3.3.1、設置永久會話
@app.route('/login', methods=['POST'])
def login(): # ... 驗證邏輯 ... session['username'] = username session.permanent = True # 開啟永久會話(默認有效期 31 天) return redirect(url_for('profile'))
3.3.2、自定義有效期
通過 app.config['PERMANENT_SESSION_LIFETIME']
自定義有效期(需導入 timedelta
):
from datetime import timedelta app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=7) # 7 天有效期