Rumah  >  Artikel  >  pembangunan bahagian belakang  >  Pengesahan FastAPI dengan Suntikan Ketergantungan

Pengesahan FastAPI dengan Suntikan Ketergantungan

Patricia Arquette
Patricia Arquetteasal
2024-09-24 06:21:09991semak imbas

FastAPI Auth with Dependency Injection

FastAPI ialah rangka kerja web moden untuk membina API dalam Python. Ia merupakan salah satu rangka kerja web kegemaran peribadi saya kerana ia mempunyai sokongan terbina dalam untuk spesifikasi OpenAPI (bermakna anda boleh menulis kod hujung belakang anda dan menjana segala-galanya daripadanya) dan ia menyokong suntikan kebergantungan.

Dalam siaran ini, kita akan melihat secara ringkas cara FastAPI's Depends berfungsi. Kemudian kita akan melihat mengapa ia digunakan dengan baik untuk pengesahan dan kebenaran. Kami juga akan membezakannya dengan perisian tengah, yang merupakan satu lagi pilihan biasa untuk pengesahan. Akhir sekali, kita akan melihat beberapa corak lanjutan untuk kebenaran dalam FastAPI.

Apakah Suntikan Ketergantungan?

Salah satu ciri FastAPI yang lebih berkuasa ialah sokongan kelas pertamanya untuk suntikan kebergantungan. Kami mempunyai panduan yang lebih panjang di sini, tetapi mari lihat contoh pantas cara ia boleh digunakan.

Katakan kita sedang membina API bernombor. Setiap panggilan API mungkin termasuk page_number dan page_size. Sekarang, kita hanya boleh mencipta API dan memasukkan parameter ini secara langsung:

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

Tetapi, kami mungkin ingin menambah beberapa logik pengesahan supaya tiada siapa yang meminta page_number -1 atau 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)

Dan ini... baik, tetapi jika kami mempunyai 10 API atau 100 API yang semuanya memerlukan param halaman yang sama, ia akan menjadi agak membosankan. Di sinilah suntikan pergantungan masuk - kita boleh memindahkan semua logik ini ke dalam fungsi dan menyuntik fungsi itu ke dalam API kami:

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)

Ini mempunyai beberapa faedah yang bagus:

  • Setiap laluan yang mengambil dalam PagingParams disahkan secara automatik dan mempunyai nilai lalai.

  • Ia kurang bertele-tele dan mudah ralat berbanding baris pertama setiap laluan validate_paging_params(page_number, page_size)

  • Ini masih berfungsi dengan sokongan OpenAPI FastAPI - parameter tersebut akan dipaparkan dalam spesifikasi OpenAPI anda.

Apakah kaitan ini dengan pengesahan?

Nampaknya, ini juga cara yang bagus untuk memodelkan auth! Bayangkan anda mempunyai fungsi seperti:

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

Untuk menghubungkan ini ke laluan API, kita hanya perlu membungkusnya dalam kebergantungan:

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

Dan kemudian semua laluan kami yang dilindungi boleh menambah pergantungan ini:

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

Jika pengguna memberikan token yang sah, laluan ini akan dijalankan dan pengguna ditetapkan. Jika tidak, 401 akan dikembalikan.

Nota: OpenAPI/Swagger memang mempunyai sokongan kelas pertama untuk menentukan token pengesahan, tetapi anda perlu menggunakan salah satu kelas khusus untuknya. Daripada req.headers["Authorization"], anda boleh menggunakan HTTPBearer(auto_error=False) daripada fastapi.security yang mengembalikan HTTPAuthorizationCredentials.

Perisian Tengah lwn Bergantung untuk Pengesahan

FastAPI, seperti kebanyakan rangka kerja, mempunyai konsep perisian tengah. Perisian tengah anda boleh mengandungi kod yang akan dijalankan sebelum dan selepas permintaan. Ia boleh mengubah suai permintaan sebelum permintaan sampai ke laluan anda dan ia boleh mengubah suai respons sebelum ia dikembalikan kepada pengguna.

Dalam banyak rangka kerja lain, perisian tengah adalah tempat yang sangat biasa untuk semakan pengesahan dilakukan. Walau bagaimanapun, itu selalunya kerana perisian tengah juga ditugaskan untuk "menyuntik" pengguna ke dalam laluan. Contohnya, corak biasa dalam Express ialah melakukan sesuatu seperti:

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
});

Memandangkan FastAPI mempunyai konsep suntikan terbina dalam, anda mungkin tidak perlu menggunakan perisian tengah sama sekali. Saya akan mempertimbangkan untuk menggunakan perisian tengah jika anda perlu "memuat semula" token pengesahan anda secara berkala (untuk memastikan ia tetap hidup) dan menetapkan respons sebagai kuki.

Dalam kes ini, anda perlu menggunakan request.state untuk menghantar maklumat daripada perisian tengah kepada laluan (dan anda boleh menggunakan kebergantungan untuk mengesahkan request.state jika anda mahu).

Jika tidak, saya akan tetap menggunakan Depends kerana pengguna akan disuntik terus ke laluan anda tanpa perlu melalui request.state.

Keizinan - Berbilang penyewaan, Peranan & Kebenaran

Jika kita menggunakan semua yang kita pelajari setakat ini, menambah dalam pelbagai penyewaan, peranan atau kebenaran boleh menjadi agak mudah. Katakan kami mempunyai subdomain unik untuk setiap pelanggan kami, kami boleh membuat pergantungan untuk subdomain ini:

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

Kami boleh menggabungkan idea ini dengan idea kami yang terdahulu dan membuat pergantungan "berbilang penyewa" baharu:

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")

Kami kemudiannya boleh menyuntik pergantungan ini ke dalam laluan kami:

@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

Dan ini akhirnya melakukan beberapa perkara utama:

  • Menyemak sama ada pengguna mempunyai token yang sah

  • Menyemak sama ada pengguna membuat permintaan kepada subdomain yang sah

  • Menyemak sama ada pengguna sepatutnya mempunyai akses kepada subdomain itu

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.

Atas ialah kandungan terperinci Pengesahan FastAPI dengan Suntikan Ketergantungan. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn