FastAPI 安全性入門 (Security Intro)

在開發現代 Web API 時,安全性 (Security) 是最重要的環節之一。無論你的 API 是提供給行動 App 使用,還是作為微服務架構的一部分,你都必須確保資料的安全性與存取控制。

在進入實裝之前,我們先釐清兩個核心概念:

  1. 驗證 (Authentication):確認使用者是誰 (你是誰?)。常見做法包括登入帳號密碼、API Key 或 Token 驗證。
  2. 授權 (Authorization):確認使用者有權做什麼 (你能做什麼?)。例如一般使用者能讀取資料,但只有管理員能刪除資料。

FastAPI 的設計目標是讓安全性實作變得簡單、標準化且強大。它直接將安全性整合到依賴注入系統與 OpenAPI 標準中。

基於業界標準:OAuth2 與 OpenAPI

FastAPI 的安全性功能並非自創,而是嚴格遵循 OAuth2 標準。這帶來了幾個巨大的優點:

  • 互操作性 (Interoperability):因為符合標準,前端、App 或第三方服務可以輕易與你的 API 對接。
  • 工具支援:現成的安全套件通常都支援 OAuth2。
  • 自動化文件:你的 Swagger UI (/docs) 會自動整合登入介面。

OAuth2 定義了多種「流程 (Flows)」,其中最常用於 Web App 的是 Password Flow:使用者傳送帳號密碼,伺服器驗證成功後回傳一個 Token。

Password Flow 主要實作步驟

  1. 建立 token 路由:定義一個 POST /token (自訂) 的路徑,使用 OAuth2PasswordRequestForm 接收登入資訊。簡單來說,這就是「登入」的 API。
  2. 密碼處理:不要儲存明文密碼,實務上應使用雜湊(Hashing)技術處理。
  3. 回傳 Token:驗證成功後,回傳一個包含 access_token 與 token_type: "bearer" 的 JSON 物件。
  4. 保護 API 路徑:使用 Depends(OAuth2PasswordBearer(tokenUrl="token")) 作為依賴項,使特定 API(如 /users/me)必須驗證 Token 才能存取。

核心組件:OAuth2PasswordBearer

要開始使用安全性功能,我們會用到 fastapi.security 模組中的 OAuth2PasswordBearer。它負責定義 Token 的來源(通常是呼叫 /token 取得)。

from typing import Annotated
from fastapi import Depends, FastAPI
from fastapi.security import OAuth2PasswordBearer

app = FastAPI()

# 定義 Token 取得的路徑為 "token"
# 這是告訴 FastAPI:若要取得 Token,請去請求 /token 接口
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

@app.get("/items/")
async def read_items(token: Annotated[str, Depends(oauth2_scheme)]):
    # 這裡的 token 會自動從 Header 的 Authorization: Bearer <token> 中取出
    return {"token": token}
tokenUrl="token" 指的是相對路徑。如果你的登入 API 位於 /auth/login,請改為 tokenUrl="auth/login"

實作 Password Flow 登入驗證

在標準 OAuth2 規範中,登入請求必須使用 Form Data (而非 JSON) 傳送 usernamepassword。FastAPI 提供了 OAuth2PasswordRequestForm 來處理這點。

安裝必要套件

由於需要處理 Form Data,必須安裝 python-multipart

pip install python-multipart

撰寫登入接口

from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordRequestForm

# 模擬的資料庫
fake_users_db = {
    "johndoe": {"username": "johndoe", "password": "secret_password"}
}

@app.post("/token")
async def login(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]):
    user_dict = fake_users_db.get(form_data.username)
    if not user_dict:
        raise HTTPException(status_code=400, detail="帳號或密碼錯誤")

    user_password = user_dict.get("password")
    if form_data.password != user_password:
        raise HTTPException(status_code=400, detail="帳號或密碼錯誤")

    # 驗證成功,必須回傳 access_token 與 token_type
    return {"access_token": form_data.username, "token_type": "bearer"}
變數名稱 access_tokentoken_type 是 OAuth2 標準定義的。

實作密碼雜湊安全性 (Hashing)

在真實專案中,絕對不能明碼儲存密碼。一旦資料庫洩漏,所有使用者的資安都會受到威脅。

使用 passlib 與 bcrypt

推薦使用 passlib 來進行密碼雜湊處理:

pip install "passlib[bcrypt]"

實作加密與驗證邏輯:

from passlib.context import CryptContext

# 指定使用 bcrypt 演算法
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

def verify_password(plain_password, hashed_password):
    return pwd_context.verify(plain_password, hashed_password)

def get_password_hash(password):
    return pwd_context.hash(password)
雜湊 (Hash) 是不可逆的,一旦遺失原始密碼,只能重新重設,無法從雜湊值「解密」回原始密碼。這正是它安全的地方。

在 Swagger UI 中測試

FastAPI 讓安全性測試變得非常直觀:

  1. 開啟瀏覽器造訪 /docs
  2. 你會看到右上角出現一個 Authorize 按鈕。
  3. 點擊按鈕,輸入你在 fake_users_db 設定的帳號密碼。
  4. 點擊 Authorize。之後所有受保護的路徑 (使用了 oauth2_scheme 的路徑),Swagger 都會自動在請求 Header 加入 Authorization: Bearer <token>

注意事項:HTTPS 是必須的

OAuth2 Password Flow 會在網路上傳輸明碼密碼(在 Form Data 中)。

在生產環境中,你必須強制使用 HTTPS。透過加密的連線,你的帳號密碼才不會被中間人 (Man-in-the-Middle) 截獲。

實務應用:取得當前使用者 (get_current_user)

目前為止,我們僅學會了如何取得 Token 字串 (username)。但在實務開發中,我們通常希望直接獲得一個完整的 使用者物件 (User Object)

這可以透過建立另一個依賴項來實現:

async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]):
    user_dict = fake_users_db.get(token) # 在這個例子中 Token 就是 username
    if not user_dict:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="無效的驗證憑證",
            headers={"WWW-Authenticate": "Bearer"},
        )
    return user_dict

@app.get("/users/me")
async def read_users_me(current_user: Annotated[dict, Depends(get_current_user)]):
    # 下方程式碼只有在 get_current_user 成功驗證後才會執行
    return current_user

為什麼要這樣設計?

  1. 層次化依賴read_users_me 依賴於 get_current_user,而 get_current_user 又依賴於 oauth2_scheme。FastAPI 會自動幫你解析這條路徑。
  2. 關注點分離:路由函數不再需要關心 Token 是怎麼來的、也不用自己去查找資料庫。它只需要宣告「我需要一個當前使用者」,FastAPI 就會幫你注入進來。
  3. 安全性:如果 Token 無效或使用者不存在,get_current_user 就會直接拋出異常並回傳 401,保護後續程式碼不被執行。

總結

  • OAuth2 是 API 安全性的業界標準,FastAPI 提供內建支援。
  • 使用 OAuth2PasswordBearer 定義驗證機制。
  • 利用 OAuth2PasswordRequestForm 處理 Form Data 格式的登入請求。
  • 密碼雜湊 (如 Bcrypt) 是保護使用者資訊的基本門檻。
  • 透過依賴注入,你可以輕鬆保護任何 API 路由。

你可以進一步參考 JWT (JSON Web Token) 章節,讓你的登入系統更加安全且具備「無狀態 (Stateless)」的特性。