本文 AI 產出,尚未審核

FastAPI 中介層(Middleware)

主題:CORS Middleware(跨來源設定)


簡介

在前後端分離的時代,前端應用程式常常會從不同的網域(Domain)呼叫 API。如果伺服器沒有正確設定跨來源資源共享(CORS, Cross‑Origin Resource Sharing),瀏覽器會直接阻擋請求,導致前端出現 “No 'Access‑Control‑Allow‑Origin' header” 的錯誤訊息。

FastAPI 內建的 CORS Middleware 能夠在所有路由之前自動加入必要的 HTTP 標頭,讓開發者只要在應用程式啟動時做一次設定,就能安全、彈性地控制哪些來源可以存取 API。這不僅提升開發效率,也避免了因手動在每個路由加上 header 而產生的錯誤或遺漏。

本篇將深入說明 CORS 的原理、在 FastAPI 中如何使用內建 Middleware,並提供多個實務範例、常見陷阱與最佳實踐,幫助你在真實專案中快速上手。


核心概念

1. 為什麼需要 CORS?

  • 瀏覽器的同源政策:出於安全考量,瀏覽器預設只允許同一來源(協定、主機、埠號)之間的請求。跨來源請求會被視為「不安全」而被阻擋。
  • 預檢請求(Preflight):對於會改變伺服器狀態的 HTTP 方法(如 POST、PUT、DELETE)或包含自訂 Header 時,瀏覽器會先發送 OPTIONS 請求檢查伺服器是否允許此類請求。若伺服器回應正確的 CORS 標頭,瀏覽器才會正式發送真正的請求。

2. CORS Header 簡介

Header 功能
Access-Control-Allow-Origin 指定允許的來源,* 代表全部。
Access-Control-Allow-Methods 允許的 HTTP 方法(GET、POST…)。
Access-Control-Allow-Headers 允許的自訂 Header(如 Authorization)。
Access-Control-Allow-Credentials 是否允許攜帶 Cookie、Authorization 等憑證。
Access-Control-Max-Age 預檢請求的快取時間(秒)。

3. FastAPI 內建的 CORS Middleware

FastAPI 使用 Starlette 作為底層框架,而 CORS Middleware 正是 Starlette 提供的 CORSMiddleware。只需要在 FastAPI 實例建立後、路由註冊前加入即可。

from fastapi import FastAPI
from starlette.middleware.cors import CORSMiddleware

app = FastAPI()

# 1️⃣ 設定允許的來源
origins = [
    "https://example.com",
    "http://localhost:3000",
]

# 2️⃣ 加入 Middleware
app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,            # 允許的來源清單
    allow_credentials=True,          # 是否允許攜帶 Cookie
    allow_methods=["*"],             # 允許所有 HTTP 方法
    allow_headers=["*"],             # 允許所有 Header
)

重點allow_origins 必須是 完整的 URL(包含協定),不能只寫 example.com,否則會被視為不合法而被忽略。


程式碼範例

以下提供 五個實用範例,說明不同情境下的 CORS 設定方式與常見需求。

範例 1️⃣:允許全部來源(開發環境快速測試)

from fastapi import FastAPI
from starlette.middleware.cors import CORSMiddleware

app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],          # ★ 允許任意來源
    allow_methods=["*"],
    allow_headers=["*"],
)

⚠️ 注意:在正式環境 千萬不要 使用 ["*"],因為會讓任何網站都能存取你的 API,安全風險極高。


範例 2️⃣:限定特定前端網域(最常見的做法)

origins = [
    "https://my-frontend.com",
    "https://admin.my-frontend.com",
]

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,      # 允許 Cookie、Authorization Header
    allow_methods=["GET", "POST", "PUT", "DELETE"],
    allow_headers=["Authorization", "Content-Type"],
)

說明

  • allow_credentials=True 讓前端可以攜帶 JWTSession Cookie
  • allow_methods 只列出實際需要的 HTTP 方法,減少攻擊面。

範例 3️⃣:動態讀取環境變數(支援多環境部署)

import os
from fastapi import FastAPI
from starlette.middleware.cors import CORSMiddleware

app = FastAPI()

# 從環境變數取得允許的來源,使用逗號分隔
origins = os.getenv("CORS_ORIGINS", "").split(",")

app.add_middleware(
    CORSMiddleware,
    allow_origins=[origin.strip() for origin in origins if origin],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

實務技巧:在 docker-compose.yml 或 CI/CD pipeline 中設定 CORS_ORIGINS,即可在不同環境(dev、staging、prod)自動切換。


範例 4️⃣:僅允許特定 Header 與方法(配合嚴格的安全政策)

app.add_middleware(
    CORSMiddleware,
    allow_origins=["https://secure-client.com"],
    allow_credentials=False,
    allow_methods=["GET", "HEAD"],
    allow_headers=["X-Custom-Header", "Authorization"],
    expose_headers=["X-RateLimit-Remaining"],   # 前端可直接讀取的自訂回應 Header
    max_age=600,                                 # 預檢結果快取 10 分鐘
)

說明

  • expose_headers 讓瀏覽器可以在 response.headers 中看到指定的回應 Header。
  • max_age 減少預檢請求次數,提升效能。

範例 5️⃣:結合自訂 Middleware,於回應中加入安全 Header

from fastapi import FastAPI, Request, Response
from starlette.middleware.cors import CORSMiddleware

app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allow_origins=["https://my-app.com"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# 自訂 Middleware:加入 Content‑Security‑Policy
@app.middleware("http")
async def add_security_headers(request: Request, call_next):
    response: Response = await call_next(request)
    response.headers["Content-Security-Policy"] = "default-src 'self'"
    return response

結合使用:CORS 解決跨域問題,額外的安全 Header(如 CSP、X‑Frame‑Options)則提升整體防護。


常見陷阱與最佳實踐

陷阱 為何會發生 解決方式
使用 ["*"] 允許所有來源 開發時方便,但在正式環境會讓任何網站盜用 API。 僅在開發環境 使用 ["*"],正式環境必須列出白名單。
allow_credentials=True 搭配 allow_origins=["*"] 瀏覽器會直接拒絕回應,因為安全政策不允許同時使用通配符與憑證。 必須 明確指定 allow_origins,或將 allow_credentials 設為 False
忘記將自訂 Header 加入 allow_headers 前端發送 AuthorizationX-Requested-With 等自訂 Header 時,被預檢請求阻擋。 allow_headers 中列出所有需要的 Header,或使用 ["*"]
預檢請求被視為未授權 若路由未正確處理 OPTIONS,預檢會失敗。 CORSMiddleware 會自動處理 OPTIONS,只要正確掛載即可。
CORS 設定遺漏在子應用(APIRouter) 子路由如果自行建立 FastAPI 實例,會繞過父層 Middleware。 確保所有子應用都使用同一個 FastAPI 實例,或在子路由上再次掛載 Middleware。

最佳實踐

  1. 白名單機制:在 allow_origins 中僅列出真實需要的前端 URL。
  2. 環境變數管理:使用 .env、Docker secret 或 CI 變數,避免硬編碼。
  3. 最小權限原則:只開放必要的 HTTP 方法與 Header。
  4. 結合安全 Header:如 CSP、X‑Content‑Type‑Options,提升整體防護。
  5. 測試預檢:使用 Postman、curl 或前端測試程式,確認 OPTIONS 回應正確。
    curl -i -X OPTIONS https://api.example.com/items \
         -H "Origin: https://my-frontend.com" \
         -H "Access-Control-Request-Method: POST" \
         -H "Access-Control-Request-Headers: Authorization"
    

實際應用場景

場景 為何需要 CORS 設定範例
單頁應用(SPA)與後端 API 分離 前端在 https://app.example.com,API 在 https://api.example.com allow_origins=["https://app.example.com"]
多租戶 SaaS 平台 每個租戶都有自己的子網域(tenant1.myapp.comtenant2.myapp.com)。 動態讀取 CORS_ORIGINS,或在路由中根據租戶驗證來源。
行動端 APP(React Native / Flutter) 手機 APP 發送 HTTP 請求時不受同源政策限制,但若使用 WebView 仍需 CORS。 allow_origins=["*"](僅限於內部測試),正式環境仍建議白名單。
第三方合作夥伴 合作夥伴的前端系統需要直接呼叫你的 API。 allow_origins 中加入合作夥伴的域名,並設定 allow_credentials=False
微服務間的內部呼叫 服務間使用內部網路,不需要跨域,但若使用瀏覽器測試工具仍會觸發預檢。 可在測試環境使用 ["*"],正式環境則限制為內部 IP。

總結

  • CORS 是瀏覽器為保護使用者而實施的同源政策,FastAPI 透過 CORSMiddleware 提供簡潔且彈性的跨來源設定方式。
  • 只要在應用程式啟動時正確掛載 Middleware,便能一次解決所有路由的跨域需求。
  • 安全第一:千萬不要在正式環境使用 ["*"],必須以白名單方式明確指定允許的來源,並配合 allow_credentialsallow_methodsallow_headers 等最小權限原則。
  • 透過 環境變數動態配置,可以在不同部署環境(開發、測試、上線)輕鬆切換 CORS 設定,減少人為錯誤。
  • 結合其他安全 Header(CSP、X‑Frame‑Options)以及 測試預檢請求,才能打造既安全又友善開發者體驗的 API。

掌握了 CORS Middleware 的使用,你就能在 FastAPI 中自信地提供跨域服務,讓前端與後端的合作更加順暢,專案開發效率大幅提升。祝你開發愉快!