本文 AI 產出,尚未審核

FastAPI – Session‑based 認證

簡介

在 Web 應用程式中,認證是確保使用者身分合法的第一道防線。除了廣受歡迎的 JWT、OAuth2 等 token‑based 機制外,Session‑based 認證 仍是許多傳統網站與內部系統的首選。它的核心概念是:伺服器在記憶體或資料庫中保存一段「會話」資料,並透過瀏覽器的 Cookie 把會話 ID 回傳給客戶端,後續每一次請求都會帶上這個 ID,讓伺服器得以辨識使用者。

FastAPI 以 Starlette 為底層框架,天然支援 SessionMiddleware,只要稍加設定,就能在 FastAPI 中實作安全、可維護的 Session 認證。本文將從概念說明、實作範例、常見陷阱與最佳實踐,帶你一步步完成 Session‑based 認證,讓你的 API 能同時支援前端 SPA、傳統表單或行動端。


核心概念

1. Session 與 Cookie 的關係

項目 Session Cookie
存放位置 伺服器端(記憶體、Redis、資料庫) 客戶端(瀏覽器)
內容 使用者身分、權限、臨時資料等 只存放會話 ID(如 session_id
安全性 只要伺服器端保護好,資料不會被竊取 必須使用 HttpOnlySecureSameSite 等屬性防止 XSS/CSRF

重點:在 Session‑based 認證中,永遠不要把敏感資訊直接寫入 Cookie,只存會話 ID,所有實際資料都放在伺服器端。

2. FastAPI 中的 SessionMiddleware

FastAPI 直接使用 starlette.middleware.sessions.SessionMiddleware,只需要提供一個 加密金鑰secret_key)即可讓 Cookie 中的 session ID 加密、簽名,防止被竊改。

# app/main.py
from fastapi import FastAPI
from starlette.middleware.sessions import SessionMiddleware

app = FastAPI()

# 只要設定一次即可,建議使用 32 位元以上隨機字串
app.add_middleware(SessionMiddleware, secret_key="YOUR_SUPER_SECRET_KEY")

提示secret_key 應該放在環境變數或密鑰管理系統中,絕不可硬寫在程式碼。

3. 依賴注入取得 Session

FastAPI 的依賴注入(Dependency Injection)讓我們可以在路由函式中直接取得 request.session,如同取得其他依賴一樣。

# app/dependencies.py
from fastapi import Request

def get_session(request: Request):
    """
    取得當前請求的 Session dict。
    若 Session 尚未建立,會自動產生空的 dict。
    """
    return request.session

4. 登入、登出與 Session 的生命週期

4.1 登入流程

  1. 客戶端送出帳號密碼(POST /login)。
  2. 後端驗證成功後,將使用者 ID(或其他必要資訊)寫入 session
  3. SessionMiddleware 會自動在回應的 Set-Cookie 標頭中加入加密過的 session ID。
# app/routes/auth.py
from fastapi import APIRouter, Depends, HTTPException, status, Request
from passlib.context import CryptContext

router = APIRouter()
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

# 假資料庫
FAKE_USERS_DB = {
    "alice": {"username": "alice", "hashed_password": pwd_context.hash("secret123")},
}

def verify_password(plain_password: str, hashed_password: str) -> bool:
    return pwd_context.verify(plain_password, hashed_password)

@router.post("/login")
async def login(request: Request, username: str, password: str):
    """
    登入 API,成功後會在 Session 中寫入 `user_id`。
    """
    user = FAKE_USERS_DB.get(username)
    if not user or not verify_password(password, user["hashed_password"]):
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED,
                            detail="帳號或密碼錯誤")

    # 登入成功 → 寫入 Session
    request.session["user_id"] = user["username"]
    # 可自行設定過期時間(秒),例如 30 分鐘
    request.session["expiry"] = 30 * 60
    return {"msg": "登入成功"}

4.2 登出流程

只要刪除 Session 中的資料或直接清空整個 Session 即可。

@router.post("/logout")
async def logout(request: Request):
    """
    登出 API,清除 Session。
    """
    request.session.clear()   # 移除所有鍵值
    return {"msg": "已登出"}

4.3 Session 過期與續期

SessionMiddleware 本身不會自動過期,我們需要自行在每次請求時檢查 expiry,若超過則清除 Session。

# app/dependencies.py
import time
from fastapi import Request, HTTPException, status

def get_current_user(request: Request):
    """
    取得目前已登入的使用者,若未登入或 Session 過期則拋出 401。
    """
    session = request.session
    user_id = session.get("user_id")
    expiry = session.get("expiry")
    if not user_id or not expiry:
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED,
                            detail="未登入或 Session 已失效")
    # 檢查過期時間
    if time.time() > session.get("created_at", time.time()) + expiry:
        session.clear()
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED,
                            detail="Session 已過期")
    # 更新最後存取時間(可選)
    session["last_access"] = time.time()
    return user_id

5. CSRF 防護

Session‑based 認證最常見的攻擊是 Cross‑Site Request Forgery (CSRF)。最簡單的防護方式是:

  1. 為所有 非 GET 請求加上自訂 Header(如 X-CSRF-Token)。
  2. 在登入成功時,把隨機產生的 CSRF Token 放入 Session,回傳給前端;前端在每次 POST/PUT/DELETE 時帶上此 Header。
  3. 後端驗證 Header 與 Session 中的 Token 是否相符。
import secrets
from fastapi import Header

# 登入成功時產生 CSRF token
@router.post("/login")
async def login(request: Request, username: str, password: str):
    # ... (驗證略)
    request.session["user_id"] = user["username"]
    csrf_token = secrets.token_urlsafe(32)
    request.session["csrf_token"] = csrf_token
    return {"msg": "登入成功", "csrf_token": csrf_token}

# 依賴檢查 CSRF
def verify_csrf(request: Request, x_csrf_token: str = Header(None)):
    session_token = request.session.get("csrf_token")
    if not session_token or session_token != x_csrf_token:
        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN,
                            detail="CSRF 驗證失敗")

# 使用範例
@router.post("/profile")
async def update_profile(
    request: Request,
    data: dict,
    csrf: None = Depends(verify_csrf),
    user_id: str = Depends(get_current_user),
):
    # 實作更新使用者資料
    return {"msg": f"{user_id} 的資料已更新"}

常見陷阱與最佳實踐

陷阱 說明 解決方案
Session 資料過大 將大量資料放入 request.session 會導致 Cookie 加密後變長,影響效能。 只存 user_idexpirycsrf_token 等少量關鍵資訊,其他資料放在資料庫或快取(Redis)。
未設定 HttpOnly / Secure 攻擊者可藉由 XSS 竊取 Cookie。 SessionMiddleware 預設會加上 HttpOnly,若在 HTTPS 環境一定設定 Secure=True
CSRF 防護不足 只靠 Cookie 會遭受跨站請求。 如上所示,實作 雙重提交 CookieSameSite=Strict
Session 失效後仍可使用 未在每次請求檢查過期時間。 在依賴 get_current_user 中加入過期檢查,或使用 starletteBackgroundTask 清除過期 Session。
密鑰外泄 secret_key 若寫在程式碼,會被意外洩漏。 使用環境變數或密鑰管理服務(Vault、AWS KMS)。

最佳實踐清單

  1. 使用 HTTPS:所有 Cookie 必須設 Secure,避免明文傳輸。
  2. 設定 SameSiteSameSite=LaxStrict 可減少 CSRF 風險。
  3. 定期輪換 secret_key:配合版本升級或密鑰管理機制。
  4. 將 Session 存在 Redis:若應用需要水平擴展,使用集中式快取避免「sticky session」問題。
  5. 限制 Session 生命週期:短時間(如 15–30 分鐘)且支援「延長」機制,提升安全性。

實際應用場景

場景 為何選擇 Session‑based 實作要點
內部管理系統(ERP、CRM) 使用者多為公司員工,需求快速登入、角色權限切換,且不需要跨域的 token。 使用 SessionMiddleware + Redis,搭配角色 ID 存於 Session。
傳統表單網站(會員中心、部落格) 前端以 HTML 表單為主,瀏覽器自動攜帶 Cookie,開發成本低。 設定 SameSite=Strict,並在每個 POST/PUT 加入 CSRF Token。
混合式 SPA + SSR 部分頁面使用 Server‑Side Rendering,部分使用 Vue/React SPA,兩者共享同一 Session。 在 SPA 初始化時從 /login 取得 CSRF token,之後所有 API 請求都帶上 Header。
多服務微服務 需要在多個服務間共享使用者會話資訊。 將 Session 存於 Redis,所有服務共用相同 secret_key 與 Redis 連線。

總結

  • Session‑based 認證 仍是許多企業內部與傳統網站的首選方案,核心在於伺服器端保存會話資訊、客戶端只攜帶加密過的 session ID。
  • FastAPI 透過 SessionMiddleware 只需簡單設定即可啟用,配合依賴注入、CSRF 防護與過期檢查,便能建立安全、可擴展的認證系統。
  • 實作時要特別注意 Cookie 安全屬性密鑰管理Session 大小CSRF 防護,並建議使用 Redis 等集中式快取以支援水平擴充。

掌握上述概念與最佳實踐,你就能在 FastAPI 中快速構建可靠的 Session‑based 認證,讓使用者體驗與系統安全同時提升。祝開發順利!