FastAPI 安全性入門 (Security Intro)
在開發現代 Web API 時,安全性 (Security) 是最重要的環節之一。無論你的 API 是提供給行動 App 使用,還是作為微服務架構的一部分,你都必須確保資料的安全性與存取控制。
在進入實裝之前,我們先釐清兩個核心概念:
- 驗證 (Authentication):確認使用者是誰 (你是誰?)。常見做法包括登入帳號密碼、API Key 或 Token 驗證。
- 授權 (Authorization):確認使用者有權做什麼 (你能做什麼?)。例如一般使用者能讀取資料,但只有管理員能刪除資料。
FastAPI 的設計目標是讓安全性實作變得簡單、標準化且強大。它直接將安全性整合到依賴注入系統與 OpenAPI 標準中。
基於業界標準:OAuth2 與 OpenAPI
FastAPI 的安全性功能並非自創,而是嚴格遵循 OAuth2 標準。這帶來了幾個巨大的優點:
- 互操作性 (Interoperability):因為符合標準,前端、App 或第三方服務可以輕易與你的 API 對接。
- 工具支援:現成的安全套件通常都支援 OAuth2。
- 自動化文件:你的 Swagger UI (/docs) 會自動整合登入介面。
OAuth2 定義了多種「流程 (Flows)」,其中最常用於 Web App 的是 Password Flow:使用者傳送帳號密碼,伺服器驗證成功後回傳一個 Token。
Password Flow 主要實作步驟
- 建立 token 路由:定義一個 POST /token (自訂) 的路徑,使用 OAuth2PasswordRequestForm 接收登入資訊。簡單來說,這就是「登入」的 API。
- 密碼處理:不要儲存明文密碼,實務上應使用雜湊(Hashing)技術處理。
- 回傳 Token:驗證成功後,回傳一個包含 access_token 與 token_type: "bearer" 的 JSON 物件。
- 保護 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) 傳送 username 和 password。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_token 和 token_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)
在 Swagger UI 中測試
FastAPI 讓安全性測試變得非常直觀:
- 開啟瀏覽器造訪
/docs。 - 你會看到右上角出現一個 Authorize 按鈕。
- 點擊按鈕,輸入你在
fake_users_db設定的帳號密碼。 - 點擊 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
為什麼要這樣設計?
- 層次化依賴:
read_users_me依賴於get_current_user,而get_current_user又依賴於oauth2_scheme。FastAPI 會自動幫你解析這條路徑。 - 關注點分離:路由函數不再需要關心 Token 是怎麼來的、也不用自己去查找資料庫。它只需要宣告「我需要一個當前使用者」,FastAPI 就會幫你注入進來。
- 安全性:如果 Token 無效或使用者不存在,
get_current_user就會直接拋出異常並回傳 401,保護後續程式碼不被執行。
總結
- OAuth2 是 API 安全性的業界標準,FastAPI 提供內建支援。
- 使用
OAuth2PasswordBearer定義驗證機制。 - 利用
OAuth2PasswordRequestForm處理 Form Data 格式的登入請求。 - 密碼雜湊 (如 Bcrypt) 是保護使用者資訊的基本門檻。
- 透過依賴注入,你可以輕鬆保護任何 API 路由。
你可以進一步參考 JWT (JSON Web Token) 章節,讓你的登入系統更加安全且具備「無狀態 (Stateless)」的特性。