首頁  >  文章  >  後端開發  >  具有依賴注入的 FastAPI 身份驗證

具有依賴注入的 FastAPI 身份驗證

Patricia Arquette
Patricia Arquette原創
2024-09-24 06:21:091000瀏覽

FastAPI Auth with Dependency Injection

FastAPI 是一個用於在 Python 中建立 API 的現代 Web 框架。它是我個人最喜歡的 Web 框架之一,因為它內建了對 OpenAPI 規範的支援(這意味著您可以編寫後端程式碼並從中生成所有內容),並且它支援依賴注入

在這篇文章中,我們將簡要介紹一下 FastAPI 的 Depends 是如何運作的。然後我們將了解為什麼它如此適用於身份驗證和授權。我們還將它與中間件進行對比,中間件是身份驗證的另一個常見選項。最後,我們將了解 FastAPI 中一些更進階的授權模式。

什麼是依賴注入?

FastAPI 更強大的功能之一是它對依賴注入的一流支援。我們有一個更長的指南這裡,但讓我們來看一個如何使用它的快速範例。

假設我們正在建立一個分頁 API。每個API呼叫可以包括page_number和page_size。現在,我們可以建立一個 API 並直接傳入這些參數:

@app.get("/things/")
async def fetch_things(page_number: int = 0, page_size: int = 100):
    return db.fetch_things(page_number, page_size)

但是,我們可能想要加入一些驗證邏輯,這樣就沒有人要求 page_number -1 或 page_size 10,000,000。

@app.get("/things/")
async def fetch_things(page_number: int = 0, page_size: int = 100):
    if page_number < 0:
        raise HTTPException(status_code=400, detail="Invalid page number")
    elif page_size <= 0:
        raise HTTPException(status_code=400, detail="Invalid page size")
    elif page_size > 100:
        raise HTTPException(status_code=400, detail="Page size can be at most 100")
    return db.fetch_things(page_number, page_size)

這…很好,但如果我們有 10 個 API 或 100 個 API 都需要相同的分頁參數,那就有點無聊了。這就是依賴注入的用武之地 - 我們可以將所有這些邏輯移至一個函數中,並將該函數注入到我們的 API 中:

async def paging_params_dep(page_number: int = 0, page_size: int = 100):
    if page_number < 0:
        raise HTTPException(status_code=400, detail="Invalid page number")
    elif page_size <= 0:
        raise HTTPException(status_code=400, detail="Invalid page size")
    elif page_size > 100:
        raise HTTPException(status_code=400, detail="Page size can be at most 100")
    return PagingParams(page_number, page_size)

@app.get("/things/")
async def fetch_things(paging_params: PagingParams = Depends(paging_params_dep)):
    return db.fetch_things(paging_params)

@app.get("/other_things/")
async def fetch_other_things(paging_params: PagingParams = Depends(paging_params_dep)):
    return db.fetch_other_things(paging_params)

這有一些不錯的好處:

  • 每條採用 PagingParams 的路由都會自動驗證並具有預設值。

  • 它比每條路由的第一行是 validate_paging_params(page_number, page_size)

  • 更簡潔,更容易出錯
  • 這仍然適用於 FastAPI 的 OpenAPI 支援 - 這些參數會顯示在您的 OpenAPI 規格中。

這與身分驗證有什麼關係?

事實證明,這也是一種建模身份驗證的好方法!想像你有一個像這樣的函數:

async def validate_token(token: str):
    try:
        # This could be JWT validation, looking up a session token in the DB, etc.
        return await get_user_for_token(token)
    except:
        return None

要將其連接到 API 路由,我們需要做的就是將其包裝在依賴項中:

async def require_valid_token_dep(req: Request):
    # This could also be a cookie, x-api-key header, etc.
    token = req.headers["Authorization"]
    user = await validate_token(token)
    if user == None:
        raise HTTPException(status_code=401, detail="Unauthorized")
    return user

然後我們所有受保護的路由都可以添加此依賴項:

@app.get("/protected")
async def do_secret_things(user: User = Depends(require_valid_token_dep)):
    # do something with the user

如果使用者提供了有效的令牌,則該路由將運行並設定使用者。否則,將返回 401。

注意:OpenAPI/Swagger 確實對指定身份驗證令牌具有一流的支持,但您必須使用其中一個專用類別。您可以使用 fastapi.security 中的 HTTPBearer(auto_error=False) 來取代 req.headers["Authorization"],它會傳回 HTTPAuthorizationCredentials。

中介軟體與 Depends for Auth

FastAPI 與大多數框架一樣,有一個中間件 的概念。您的中間件可以包含將在請求之前和之後運行的程式碼。它可以在請求到達您的路由之前修改請求,也可以在回應傳回給使用者之前修改回應。

在許多其他框架中,中間件是進行身份驗證檢查的非常常見的地方。然而,這通常是因為中間件也負責將使用者「注入」到路由中。例如,Express 中的常見模式是執行以下操作:

app.get("/protected", authMiddleware, (req, res) => {
    // req.user is set by the middleware
    // as there's no good way to pass in extra information into this route,
    // outside of the request
});

由於FastAPI具有內建的注入概念,因此您可能根本不需要使用中介軟體。如果您需要定期「刷新」您的身份驗證令牌(以使其保持活動狀態)並將回應設定為 cookie,我會考慮使用中間件。

在這種情況下,您需要使用 request.state 將資訊從中間件傳遞到路由(如果您願意,您可以使用依賴項來驗證 request.state)。

否則,我會堅持使用 Depends,因為用戶將直接注入到您的路由中,而不需要通過 request.state。

授權 - 多租用戶、角色和權限

如果我們應用迄今為止所學到的一切,添加多租戶、角色或權限可能會非常簡單。假設我們為每個客戶都有一個唯一的子網域,我們可以為該子網域建立依賴關係:

async def tenant_by_subdomain_dep(request: Request) -> Optional[str]:
    # first we get the subdomain from the host header
    host = request.headers.get("host", "")
    parts = host.split(".")
    if len(parts) <= 2:
        raise HTTPException(status_code=404, detail="Not found")
    subdomain = parts[0]

    # then we lookup the tenant by subdomain
    tenant = await lookup_tenant_for_subdomain(subdomain)
    if tenant == None:
        raise HTTPException(status_code=404, detail="Not found")
    return tenant

我們可以將這個想法與先前的想法結合起來,製作一個新的「多租戶」依賴:

async def get_user_and_tenant_for_token(
    user: User = Depends(require_valid_token_dep),
    tenant: Tenant = Depends(tenant_by_subdomain_dep),
) -> UserAndTenant:
    is_user_in_tenant = await check_user_is_in_tenant(tenant, user)
    if is_user_in_tenant:
        return UserAndTenant(user, tenant)
    raise HTTPException(status_code=403, detail="Forbidden")

然後我們可以將此依賴項注入到我們的路由中:

@app.get("/protected")
async def do_secret_things(user_and_tenant: UserAndTenant = Depends(get_user_and_tenant_for_token)):
    # do something with the user and tenant

這最終會做一些主要的事情:

  • 檢查使用者是否擁有有效令牌

  • 檢查使用者是否正在向有效的子網域發出請求

  • 檢查使用者是否應該有權利存取該子網域

If any of those invariants aren’t met - an error is returned and our route will never run. We can extend this to include other things like roles & permissions (RBAC) or making sure the user has a certain property set (active paid subscription vs no active subscription).

PropelAuth <3 FastAPI

At PropelAuth, we’re big fans of FastAPI. We have a FastAPI library that will enable you to set up authentication and authorization quickly - including SSO, Enterprise SSO / SAML, SCIM Provisioning, and more.

And it all works with dependencies like the ones you’ve seen above, e.g.:

@app.get("/")
async def root(current_user: User = Depends(auth.require_user)):
    return {"message": f"Hello {current_user.user_id}"}

You can find out more here.

Summary

  • FastAPI's dependency injection provides a powerful way to handle authentication and authorization in web applications.

  • The Depends feature allows for clean, reusable code for validating tokens, checking user permissions, and handling multi-tenancy.

  • Compared to middleware, using dependencies for auth offers more flexibility and direct integration with route functions.

  • Complex authorization scenarios like multi-tenancy and role-based access control can be efficiently implemented using nested dependencies.

  • PropelAuth offers a FastAPI library that simplifies the implementation of advanced authentication and authorization features.

以上是具有依賴注入的 FastAPI 身份驗證的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn