FastAPI 課程:Session 與 Cookie 管理
主題:回傳 Response 時設定 Cookie
簡介
在 Web 應用程式中,Cookie 是最常見的客戶端狀態保存機制之一。它可以用來記錄使用者的登入資訊、偏好設定、追蹤分析資料等。FastAPI 作為一個高效能的 Python Web 框架,提供了直觀且彈性的方式在 Response 中設定、讀取與刪除 Cookie,讓開發者可以輕鬆完成 Session 管理、驗證流程等需求。
本篇文章將從 概念說明、實作範例、常見陷阱與最佳實踐,以及 實務應用場景 四個面向,完整介紹在 FastAPI 中如何於回傳 Response 時設定 Cookie,幫助初學者快速上手,同時提供中階開發者更深入的技巧與注意事項。
核心概念
1. Cookie 的基本原理
- Cookie 是由伺服器在 HTTP 回應的
Set-Cookie標頭中設定,瀏覽器收到後會自動在後續對同一站點的請求中帶上Cookie標頭。 - 每個 Cookie 包含 名稱 (name)、值 (value)、以及可選的屬性,如
Expires、Max-Age、Domain、Path、Secure、HttpOnly、SameSite等。 - 正確設定屬性可以提升安全性(例如
HttpOnly防止 JavaScript 讀取)與相容性(例如SameSite防止 CSRF)。
2. FastAPI 中的 Response 物件
FastAPI 內部使用 Starlette 作為底層 ASGI 框架,fastapi.Response 繼承自 starlette.responses.Response。
- 直接建立
Response、JSONResponse、HTMLResponse等物件時,都可以透過set_cookie()方法加入 Cookie。 - 若使用 依賴注入 (Dependency Injection) 或 路由函式回傳字典,FastAPI 會自動轉換成
JSONResponse,此時仍可在函式內取得Response物件並設定 Cookie。
3. Response.set_cookie() 參數說明
| 參數 | 型別 | 說明 |
|---|---|---|
key |
str |
Cookie 名稱 |
value |
str |
Cookie 值 |
max_age |
int | None |
Cookie 的存活秒數,None 表示使用 expires |
expires |
int | datetime | None |
絕對過期時間 |
path |
str |
Cookie 所屬路徑,預設 / |
domain |
str | None |
所屬網域,如 .example.com |
secure |
bool |
是否僅在 HTTPS 中傳送 |
httponly |
bool |
是否禁止 JavaScript 存取 |
samesite |
str | None |
lax、strict、none,控制跨站請求行為 |
4. 取得與刪除 Cookie
- 取得:在路由函式中,可透過
request.cookies.get("cookie_name")取得。 - 刪除:使用
response.delete_cookie(key, path='/', domain=None),實際上是設定一個已過期的 Cookie。
程式碼範例
以下範例均以 Python 3.9+、FastAPI 0.110+ 為前提,建議使用 uvicorn 作為 ASGI 伺服器。
範例 1:最簡單的 Set-Cookie
from fastapi import FastAPI, Response
app = FastAPI()
@app.get("/set-cookie")
def set_simple_cookie(response: Response):
"""
在回應中設定一個名為 `my_cookie`、值為 `hello` 的 Cookie。
"""
response.set_cookie(key="my_cookie", value="hello")
return {"message": "Cookie 已設定"}
說明:
- 只要在路由函式參數中加入
response: Response,FastAPI 會自動注入Response物件。set_cookie只需要key與value,其餘屬性使用預設值(path='/'、secure=False等)。
範例 2:設定 HttpOnly 與 Secure
from fastapi import FastAPI, Response
app = FastAPI()
@app.post("/login")
def login(response: Response, username: str, password: str):
"""
假設已完成使用者驗證,接著發放 JWT 並以 HttpOnly Cookie 回傳。
"""
# 這裡省略驗證邏輯,直接產生 token
token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
# 設定 Cookie,僅在 HTTPS 下傳送,且無法被 JS 讀取
response.set_cookie(
key="access_token",
value=token,
httponly=True,
secure=True, # 只在 HTTPS 中傳送
max_age=3600, # 1 小時後過期
samesite="lax"
)
return {"message": "登入成功"}
重點:
httponly=True能防止 XSS 攻擊竊取 token。secure=True確保 Cookie 只在加密連線下傳送,避免被中間人竊聽。
範例 3:同時回傳 JSON 與多個 Cookie
from fastapi import FastAPI, Response
app = FastAPI()
@app.get("/multi-cookie")
def multi_cookie(response: Response):
"""
同時設定使用者偏好與語系資訊的兩個 Cookie。
"""
response.set_cookie(key="theme", value="dark", max_age=30*24*60*60) # 30 天
response.set_cookie(key="locale", value="zh-TW", max_age=30*24*60*60)
return {"message": "多個 Cookie 已設定"}
說明:
set_cookie可以被呼叫多次,FastAPI 會在同一個回應中產生多個Set-Cookie標頭。
範例 4:讀取 Cookie 並回傳相應資料
from fastapi import FastAPI, Request, HTTPException
app = FastAPI()
@app.get("/profile")
def get_profile(request: Request):
"""
讀取 `access_token` Cookie,若不存在則拋出 401。
"""
token = request.cookies.get("access_token")
if not token:
raise HTTPException(status_code=401, detail="未登入")
# 假設此處會驗證 token 並取得使用者資訊
user_info = {"username": "alice", "role": "admin"}
return {"user": user_info}
提示:
request.cookies為MutableMapping[str, str],直接使用get取得值。
範例 5:刪除 Cookie(登出)
from fastapi import FastAPI, Response
app = FastAPI()
@app.post("/logout")
def logout(response: Response):
"""
透過 `delete_cookie` 讓瀏覽器移除 `access_token`。
"""
response.delete_cookie(key="access_token", path="/")
return {"message": "已登出"}
說明:
delete_cookie會設定一個已過期的同名 Cookie,瀏覽器收到後會自動刪除。
常見陷阱與最佳實踐
| 陷阱 | 可能的後果 | 解決方式 / 最佳實踐 |
|---|---|---|
忘記設定 httponly |
XSS 攻擊者可透過 JavaScript 竊取敏感資訊(如 JWT) | 預設將所有認證相關 Cookie 設為 httponly=True |
在開發環境使用 secure=True |
本機測試時無法收到 Cookie,導致認證失效 | 在本機開發時可使用環境變數切換 secure,或使用 if not DEBUG: secure=True |
SameSite=None 卻未同時設定 Secure |
現代瀏覽器會拒絕該 Cookie,導致跨站請求失敗 | 規則:若 SameSite="none" 必須同時 secure=True |
| Cookie 大小超過 4KB | 瀏覽器會截斷或直接拋棄,導致資料遺失 | 建議:僅存放必要資訊,使用伺服器端 Session 或 JWT 代替大型資料 |
使用相同 key 卻不同 path |
可能產生多個同名 Cookie,造成混淆 | 統一 path(通常為 /)或在設計時明確區分命名空間 |
未設定 max_age 或 expires |
Cookie 為 Session Cookie,關閉瀏覽器即失效 | 根據需求決定存活時間,若需要長期記錄則設定 max_age(秒)或 expires(datetime) |
最佳實踐清單
- 安全第一:所有認證或敏感資料的 Cookie 必須
httponly=True、secure=True、samesite="lax"(或strict)。 - 最小化 Cookie 大小:僅存放 token、session id,其他資訊放在資料庫或快取。
- 統一命名規則:例如
app_{purpose},避免與第三方或前端自行設定的 Cookie 衝突。 - 環境切換:使用
settings.py或環境變數管理secure、domain、samesite等屬性。 - 測試跨瀏覽器相容性:特別留意 Safari、Chrome 近期對
SameSite的實作差異。
實際應用場景
1. 使用者登入與 JWT Cookie
在大多數單頁應用(SPA)中,前端會在每次 API 請求時自動帶上 Authorization: Bearer <token>。若希望降低前端管理的複雜度,可改為 將 JWT 放入 HttpOnly Cookie,讓瀏覽器自動傳送。
- 優點:前端不需要手動儲存或注入 token,降低 XSS 風險。
- 缺點:若需要在前端取得使用者資訊,需要額外提供
/me之類的 API 讀取 Cookie 中的 token。
2. 語系與主題偏好
網站常會根據使用者的語系 (locale) 或主題 (theme) 設定 UI。將這類資訊寫入 長期 Cookie(如 30 天)可讓使用者每次訪問時自動套用個人化設定,且不需要登入。
response.set_cookie(key="theme", value="light", max_age=30*24*60*60)
response.set_cookie(key="locale", value="en-US", max_age=30*24*60*60)
3. 防止 CSRF 的 Double Submit Cookie
在某些不想使用 CSRF Token 的情況下,可採用 Double Submit Cookie:在登入時同時在 Cookie 與表單隱藏欄位寫入同一隨機字串,伺服器驗證兩者是否一致。
- 實作要點:
response.set_cookie(key="csrf_token", value=rand_str, httponly=False, samesite="strict")- 前端在每次表單提交時,將 Cookie 中的
csrf_token讀取並放入隱藏欄位。 - 伺服器比對兩者是否相同。
4. 多租戶 (SaaS) 系統的 Domain Cookie
若系統支援 子域名(如 tenant1.example.com、tenant2.example.com),可在設定 Cookie 時指定 domain=".example.com",讓所有子域共享同一個認證 Cookie。
response.set_cookie(
key="session_id",
value=session_id,
domain=".example.com",
path="/",
httponly=True,
secure=True,
samesite="lax"
)
總結
- Cookie 是瀏覽器與伺服器之間最簡單、最廣泛使用的狀態保存方式。
- 在 FastAPI 中,只需要在路由函式取得
Response物件,呼叫set_cookie()、delete_cookie()即可完成設定與刪除。 - 安全屬性(
httponly、secure、samesite)是防止 XSS、CSRF、MITM 攻擊的關鍵,務必在所有認證相關 Cookie 中啟用。 - 透過 範例 我們展示了從最簡單的單一 Cookie、到多 Cookie、到 JWT 認證與跨域設定的完整流程。
- 最佳實踐 包括:最小化 Cookie 大小、統一命名、環境化設定、測試相容性,這些都能讓你的應用在開發與上線後更安全、更穩定。
掌握了上述概念與技巧,你就能在 FastAPI 專案中自如地管理 Session 與 Cookie,為使用者提供流暢且安全的體驗。祝開發順利!