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。
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).
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.
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中文網其他相關文章!