跳至內容

安全性 - 第一步驟

假設您的後端 API 位於某個網域。

而您的前端位於另一個網域或相同網域的不同路徑(或是在行動應用程式中)。

您希望前端能夠使用使用者名稱密碼向後端進行驗證。

我們可以使用 OAuth2 搭配 FastAPI 來建構這個功能。

但讓我們省去您閱讀完整冗長規格的時間,只為了找到您需要的那些小資訊。

讓我們使用 FastAPI 提供的工具來處理安全性。

外觀如何

讓我們先使用程式碼,看看它是如何運作的,然後我們再回頭了解發生了什麼事。

建立 main.py

將範例複製到檔案 main.py

from typing import Annotated

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

app = FastAPI()

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")


@app.get("/items/")
async def read_items(token: Annotated[str, Depends(oauth2_scheme)]):
    return {"token": token}
from fastapi import Depends, FastAPI
from fastapi.security import OAuth2PasswordBearer
from typing_extensions import Annotated

app = FastAPI()

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")


@app.get("/items/")
async def read_items(token: Annotated[str, Depends(oauth2_scheme)]):
    return {"token": token}

提示

如果可能,建議使用 Annotated 版本。

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

app = FastAPI()

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")


@app.get("/items/")
async def read_items(token: str = Depends(oauth2_scheme)):
    return {"token": token}

執行它

資訊

當您執行 pip install "fastapi[standard]" 命令時,python-multipart 套件會自動與 FastAPI 一起安裝。

但是,如果您使用 pip install fastapi 命令,則預設不會包含 python-multipart 套件。

要手動安裝它,請確保您建立了一個虛擬環境,啟動它,然後使用以下指令安裝它

$ pip install python-multipart

這是因為 OAuth2 使用「表單資料」來傳送 usernamepassword

使用以下指令執行範例

$ fastapi dev main.py

<span style="color: green;">INFO</span>:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)

檢查它

前往互動式文件:http://127.0.0.1:8000/docs

您會看到類似這樣的畫面

「授權按鈕!」

您已經擁有一個閃亮的新「授權」按鈕。

而且您的路徑操作在右上角有一個小鎖,您可以點擊它。

如果您點擊它,您會看到一個小的授權表單,可以輸入 usernamepassword(以及其他選用欄位)

注意

您在表單中輸入什麼內容並不重要,它還不會運作。但我們會逐步完成。

這當然不是最終使用者的前端,但它是一個很棒的自動工具,可以互動式地記錄您的所有 API。

它可以被前端團隊使用(也可以是您自己)。

它可以被第三方應用程式和系統使用。

它也可以被您自己使用,來除錯、檢查和測試相同的應用程式。

password 流程

現在讓我們回顧一下,了解這一切是什麼。

password 「流程」是 OAuth2 中定義的處理安全性和驗證的方法(「流程」)之一。

OAuth2 的設計是為了讓後端或 API 可以獨立於驗證使用者的伺服器。

但在這種情況下,同一個 FastAPI 應用程式將會處理 API 和驗證。

所以,讓我們從這個簡化的角度來審視它。

  • 使用者在前端輸入 usernamepassword,然後按下 Enter 鍵。
  • 前端(在使用者的瀏覽器中運行)將 usernamepassword 發送到我們 API 中的特定網址(以 tokenUrl="token" 宣告)。
  • API 會檢查 usernamepassword,並以「token」(權杖)回應(我們尚未實作任何這些部分)。
    • 「token」只是一個包含一些內容的字串,我們稍後可以用它來驗證這個使用者。
    • 通常,token 會設定在一段時間後過期。
      • 因此,使用者稍後必須再次登入。
      • 而且如果 token 被盜,風險會比較小。它不像永久金鑰那樣可以永遠有效(在大多數情況下)。
  • 前端會將該 token 暫時儲存在某個地方。
  • 使用者點擊前端以進入前端網頁應用程式的另一個區段。
  • 前端需要從 API 擷取更多資料。
    • 但該特定端點需要驗證。
    • 因此,為了向我們的 API 進行驗證,它會發送一個 Authorization 標頭,其值為 Bearer 加上 token。
    • 如果 token 包含 foobar,則 Authorization 標頭的內容將會是:Bearer foobar

FastAPIOAuth2PasswordBearer

FastAPI 提供了不同抽象層級的幾種工具來實作這些安全功能。

在此範例中,我們將使用 OAuth2,採用 Password 流程,並使用 Bearer token。我們使用 OAuth2PasswordBearer 類別來做到這一點。

資訊

「Bearer」token 並不是唯一的選項。

但它最適合我們的使用案例。

而且它可能最適合大多數使用案例,除非您是 OAuth2 專家,並且確切知道為什麼有另一個選項更適合您的需求。

在這種情況下,FastAPI 也提供了建構它的工具。

當我們建立 OAuth2PasswordBearer 類別的實例時,我們會傳入 tokenUrl 參數。此參數包含客戶端(在使用者瀏覽器中執行的前端)將用於發送 usernamepassword 以取得 token 的網址。

from typing import Annotated

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

app = FastAPI()

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")


@app.get("/items/")
async def read_items(token: Annotated[str, Depends(oauth2_scheme)]):
    return {"token": token}
from fastapi import Depends, FastAPI
from fastapi.security import OAuth2PasswordBearer
from typing_extensions import Annotated

app = FastAPI()

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")


@app.get("/items/")
async def read_items(token: Annotated[str, Depends(oauth2_scheme)]):
    return {"token": token}

提示

如果可能,建議使用 Annotated 版本。

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

app = FastAPI()

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")


@app.get("/items/")
async def read_items(token: str = Depends(oauth2_scheme)):
    return {"token": token}

提示

這裡的 tokenUrl="token" 指的是我們尚未建立的相對網址 token。由於它是相對網址,因此相當於 ./token

因為我們使用的是相對網址,如果您的 API 位於 https://example.com/,則它將會指的是 https://example.com/token。但如果您的 API 位於 https://example.com/api/v1/,則它將會指的是 https://example.com/api/v1/token

使用相對網址很重要,以確保您的應用程式即使在像反向代理後方 這樣的高級使用案例中也能繼續運作。

此參數不會建立該端點/*路徑操作*,而是宣告網址 /token 將是客戶端應該用來取得 token 的網址。該資訊會用於 OpenAPI 中,然後用於互動式 API 文件系統中。

我們很快也會建立實際的路徑操作。

資訊

如果您是一位非常嚴格的「Pythonista」,您可能會不喜歡參數名稱 tokenUrl 的風格,而不是 token_url

這是因為它使用與 OpenAPI 規範中相同的名稱。這樣,如果您需要進一步研究這些安全機制中的任何一種,您只需複製並貼上它即可找到更多相關資訊。

oauth2_scheme 變數是一個 OAuth2PasswordBearer 的實例,但它同時也是一個「可呼叫的」物件。

它可以像這樣被呼叫:

oauth2_scheme(some, parameters)

所以,它可以與 Depends 一起使用。

使用它

現在,您可以將 oauth2_scheme 作為依賴項傳遞給 Depends

from typing import Annotated

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

app = FastAPI()

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")


@app.get("/items/")
async def read_items(token: Annotated[str, Depends(oauth2_scheme)]):
    return {"token": token}
from fastapi import Depends, FastAPI
from fastapi.security import OAuth2PasswordBearer
from typing_extensions import Annotated

app = FastAPI()

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")


@app.get("/items/")
async def read_items(token: Annotated[str, Depends(oauth2_scheme)]):
    return {"token": token}

提示

如果可能,建議使用 Annotated 版本。

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

app = FastAPI()

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")


@app.get("/items/")
async def read_items(token: str = Depends(oauth2_scheme)):
    return {"token": token}

這個依賴項會提供一個 str 字串,它會被賦值給 *路徑操作函式* 的 token 參數。

FastAPI 會知道它可以使用這個依賴項在 OpenAPI 模式(以及自動生成的 API 文件)中定義一個「安全機制」。

「技術細節」

FastAPI 會知道它可以使用在依賴項中宣告的 OAuth2PasswordBearer 類別來定義 OpenAPI 中的安全機制,因為它繼承自 fastapi.security.oauth2.OAuth2,而後者又繼承自 fastapi.security.base.SecurityBase

所有與 OpenAPI(以及自動生成的 API 文件)整合的安全工具都繼承自 SecurityBase,這就是 FastAPI 知道如何將它們整合到 OpenAPI 的方式。

它的作用

它會在請求中查找 Authorization 標頭,檢查其值是否為 Bearer 加上一個 token,並將 token 作為 str 字串返回。

如果它沒有看到 Authorization 標頭,或者該值不包含 Bearer token,它會直接回應一個 401 狀態碼錯誤(UNAUTHORIZED,未授權)。

您甚至不需要檢查 token 是否存在就返回錯誤。您可以確定,如果您的函式被執行,它在 token 中將會有一個 str 字串。

您可以在互動式文件中嘗試一下

我們還沒有驗證 token 的有效性,但這已經是一個開始。

回顧

因此,只需額外 3 或 4 行程式碼,您就已經擁有一些基本的安全機制。