本文 AI 產出,尚未審核

FastAPI – 安全性(Security)

主題:HTTPS 與 CORS 配置


簡介

在 Web API 的開發過程中,資料傳輸的安全性與**跨來源資源共享(CORS)**的正確設定是不可或缺的兩大基礎。

  • 若 API 直接以 HTTP 提供服務,任何人在傳輸途中都可能竊取或竄改資料,對使用者與企業都會造成嚴重風險。
  • 而前端應用(如 React、Vue)與後端 API 常常位於不同的網域或埠號,未妥善設定 CORS 會導致瀏覽器阻擋合法請求,影響使用者體驗與開發效率。

FastAPI 本身支援簡潔的安全機制,配合 uvicorngunicornnginx 等反向代理,我們可以快速完成 HTTPSCORS 的設定,讓服務既安全又易於整合前端。本文將從概念說明、實作範例、常見陷阱與最佳實踐,完整介紹在 FastAPI 中如何正確配置 HTTPS 與 CORS。


核心概念

1️⃣ 為什麼需要 HTTPS

  • 加密傳輸:TLS (Transport Layer Security) 會把所有請求與回應內容加密,防止中間人攻擊(MITM)。
  • 驗證伺服器身份:憑證(Certificate)由受信任的 CA 簽發,讓客戶端確定連線的是正確的伺服器。
  • SEO 與瀏覽器要求:現代瀏覽器會對未使用 HTTPS 的 API 標示「不安全」,影響使用者信任度。

在開發階段,我們可以使用 自簽憑證 進行測試;上線時則必須使用正式的憑證(如 Let's Encrypt)。

2️⃣ CORS 基礎

CORS 是瀏覽器的安全機制,允許伺服器告訴瀏覽器哪些來源(origin)可以存取資源。

  • Origin:由 scheme://host:port 組成,例如 https://example.com:443
  • Preflight Request:對於需要特殊權限的請求(如 PUTDELETE、或自訂 Header),瀏覽器會先發送 OPTIONS 請求確認是否允許。

FastAPI 內建 fastapi.middleware.cors.CORSMiddleware,只要在應用程式建立時加入即可。

3️⃣ HTTPS 與 CORS 的關係

  • HTTPS 必須先於 CORS 生效:若伺服器本身不支援 HTTPS,瀏覽器會直接阻擋請求,CORS 設定再怎麼正確也無法發揮作用。
  • CORS Header 必須在 TLS 加密後傳送:這確保了來源驗證資訊不會被竊聽或竄改。

程式碼範例

以下範例示範如何在 FastAPI 中同時設定 HTTPS(使用自簽憑證)與 CORS,並提供常見的變化寫法。

📌 範例 1:最簡單的 HTTPS + CORS 設定(開發環境)

# main.py
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

# -------- CORS 設定 ----------
origins = [
    "http://localhost:3000",      # React 開發伺服器
    "http://127.0.0.1:3000",
]

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,            # 允許的來源清單
    allow_credentials=True,          # 允許 Cookie / Authorization Header
    allow_methods=["*"],             # 允許所有 HTTP 方法
    allow_headers=["*"],             # 允許所有 Header
)

@app.get("/ping")
async def ping():
    return {"message": "pong"}

在專案根目錄產生自簽憑證(cert.pemkey.pem):

# 產生自簽憑證(測試用)
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout key.pem -out cert.pem -subj "/CN=localhost"

使用 uvicorn 直接啟動 HTTPS:

uvicorn main:app --host 0.0.0.0 --port 8443 --ssl-keyfile=key.pem --ssl-certfile=cert.pem

重點:開發階段只需要自簽憑證即可;若要在瀏覽器測試,請在瀏覽器中加入例外信任此憑證。


📌 範例 2:使用 Nginx 作為反向代理,提供正式的 HTTPS

Nginx 設定檔(/etc/nginx/sites-available/api.conf

server {
    listen 80;
    server_name api.example.com;
    # 讓 HTTP 自動導向 HTTPS
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name api.example.com;

    ssl_certificate     /etc/letsencrypt/live/api.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_ciphers         HIGH:!aNULL:!MD5;

    location / {
        proxy_pass http://127.0.0.1:8000;   # uvicorn 只跑在 http
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

uvicorn 啟動指令(僅 HTTP)

uvicorn main:app --host 127.0.0.1 --port 8000

技巧proxy_set_header X-Forwarded-Proto $scheme; 讓 FastAPI 能透過 request.url.scheme 正確取得 https,避免在產生絕對 URL 時出錯。


📌 範例 3:動態取得允許的 Origin(多租戶環境)

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

app = FastAPI()

# 允許所有子網域的動態判斷
def get_allowed_origins(request: Request):
    origin = request.headers.get("origin")
    if origin and origin.endswith(".mycompany.com"):
        return [origin]
    return []   # 不允許其他來源

@app.middleware("http")
async def cors_dynamic(request: Request, call_next):
    allowed = get_allowed_origins(request)
    if allowed:
        app.add_middleware(
            CORSMiddleware,
            allow_origins=allowed,
            allow_credentials=True,
            allow_methods=["GET", "POST", "PUT", "DELETE"],
            allow_headers=["*"],
        )
    response = await call_next(request)
    return response

說明:此範例展示如何根據請求的 Origin 動態決定是否加入 CORS 中介軟體,適合 SaaS 多租戶系統。注意:在高併發環境中頻繁呼叫 add_middleware 可能造成效能問題,建議在啟動時就建立完整的白名單或使用自訂 Middleware 處理 Header。


📌 範例 4:在 FastAPI 中手動設定 CORS Header(不使用 Middleware)

from fastapi import FastAPI, Response

app = FastAPI()

@app.options("/{full_path:path}")
async def preflight(full_path: str, response: Response):
    # 手動回傳 CORS Preflight 必要的 Header
    response.headers["Access-Control-Allow-Origin"] = "*"
    response.headers["Access-Control-Allow-Methods"] = "GET,POST,PUT,DELETE,OPTIONS"
    response.headers["Access-Control-Allow-Headers"] = "Authorization,Content-Type"
    response.headers["Access-Control-Max-Age"] = "86400"
    return response

適用情境:當你只想允許非常少數的路由或需要與其他非 FastAPI 框架共存時,手動設定 Header 能提供更細緻的控制。


📌 範例 5:在測試環境中關閉 CORS(單元測試)

# test_main.py
from fastapi.testclient import TestClient
from main import app

client = TestClient(app)

def test_ping():
    response = client.get("/ping")
    assert response.status_code == 200
    assert response.json() == {"message": "pong"}
    # 確認 CORS Header 正確回傳
    assert response.headers["access-control-allow-origin"] == "*"

說明TestClient 會自動使用 app 的 Middleware,讓我們在單元測試時也能驗證 CORS 設定是否正確。


常見陷阱與最佳實踐

常見問題 為何會發生 解決方式 最佳實踐
瀏覽器顯示「Mixed Content」 前端使用 HTTPS,但 API 仍以 HTTP 提供 透過 Nginx 或其他反向代理提供 HTTPS,或在開發時使用 --ssl-* 參數 務必在所有環境使用相同的協議(全站 HTTPS)
CORS 設定過於寬鬆allow_origins=["*"] 為了測試方便直接放行所有來源 在正式環境改為白名單或動態檢查 最小權限原則:僅允許需要的前端來源
Preflight 請求失敗405 Method Not Allowed 未正確加入 OPTIONS 處理或 CORS Middleware 未啟用 確認 CORSMiddleware 已加入,或自行實作 @app.options 始終允許 OPTIONS,尤其在使用自訂 Header 時
HTTPS 只在開發機可用,部署後出現 502 反向代理未正確轉發 X-Forwarded-Proto,導致 FastAPI 判斷為 HTTP 在 Nginx 加入 proxy_set_header X-Forwarded-Proto $scheme; 在程式內使用 request.url.scheme 判斷,或在 uvicorn 端加入 --proxy-headers
憑證過期未自動更新 手動產生的自簽憑證或 Let's Encrypt 未設定自動續期 使用 certbot renew --dry-run 測試,自動化腳本加入 crontab 自動化憑證管理,避免服務中斷

其他最佳實踐

  1. 使用 HTTP/2:在 Nginx 開啟 http2,提升效能與安全性。
  2. 啟用 HSTS:在 Nginx 加入 add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
  3. 限制 TLS 版本:只允許 TLS 1.2+,避免舊版協議的弱點。
  4. 定期掃描安全漏洞:使用 sslscannmap 檢查憑證與加密套件。
  5. 在 API 回傳前過濾敏感資訊:即使使用 HTTPS,仍應避免在回應中洩漏密碼、金鑰等。

實際應用場景

場景 為何需要 HTTPS & CORS 實作要點
行動 App 與後端 API 手機網路常在公共 Wi‑Fi,必須防止資料被竊聽 使用正式憑證、允許 app.example.com 作為唯一 Origin
SPA 前端(React/Vue)部署於 CDN 前端與 API 分屬不同子網域,跨域請求不可避免 在 CDN 設定 Access-Control-Allow-Origin,FastAPI 只允許該 CDN 網域
多租戶 SaaS 平台 每個客戶有自己的子網域(client1.myapp.com 動態判斷 Origin,或在 Nginx 以變數 map 產生白名單
內部微服務間的呼叫 服務間傳遞機密資料,需要加密保護 使用內部 CA 簽發憑證,並在 Nginx 中僅允許特定 IP 的 CORS(或直接關閉)
第三方合作夥伴 API 允許合作夥伴的前端直接呼叫你的 API 在 CORS 白名單中加入合作夥伴的網域,並使用 OAuth2API Key 做身份驗證

總結

  • HTTPS 為 API 提供了傳輸層的加密與身份驗證,是現代 Web 應用的基本要求。
  • CORS 則是瀏覽器層面的授權機制,必須在 HTTPS 正常運作後正確設定,才能讓前端安全地存取 API。
  • FastAPI 透過 CORSMiddleware 以及與 uvicornNginx 的配合,能在 開發、測試、上線 三個階段快速完成安全配置。
  • 最佳實踐:使用正式憑證、最小化 CORS 白名單、啟用 HSTS、限制 TLS 版本,並在部署前做好自動化測試與安全掃描。

掌握了 HTTPS 與 CORS 的正確配置後,你的 FastAPI 專案將具備 資料保密性、完整性與跨域可用性,為下一階段的認證、授權(OAuth2、JWT)奠定堅實基礎。祝開發順利,服務安全!