Penyelesaian Konteks Penyewa dan Middleware
Kenal pasti penyewa semasa daripada subdomain atau token dan sebarkan konteks melalui setiap kebergantungan.
Penyelesaian Konteks Penyewa dan Middleware ialah pelajaran Kem Intensif Pembangunan Bahagian Belakang FastAPI percuma di CoddyKit. Ini ialah pelajaran 2 daripada 4. Sebanyak 3 pelajaran dalam laluan pembelajaran ini boleh dibaca sepenuhnya secara percuma — selepas itu, CoddyKit PRO membuka akses kepada semua pelajaran, serta latihan praktikal dengan penyunting kod terbina dalam dan tutor kecerdasan buatan yang tersedia 24/7. Pelajaran ini merupakan sebahagian daripada laluan pembelajaran Kem Intensif Pembangunan Bahagian Belakang FastAPI, dan kemajuan anda disegerakkan merentas web serta aplikasi CoddyKit. Kursus Kem Intensif Pembangunan Bahagian Belakang FastAPI merangkumi sejumlah 4 pelajaran.
Mengapa Konteks Penyewa Penting
Dalam SaaS berbilang penyewa, satu proses FastAPI melayan ramai pelanggan. Setiap permintaan mesti ditetapkan skopnya kepada tepat satu penyewa, dan kesilapan di sini akan membocorkan data merentas organisasi.
Masalah terasnya: apabila pertanyaan dijalankan jauh di dalam perkhidmatan atau repositori, kod perlu mengetahui penyewa yang diwakilinya. Kami menyelesaikannya melalui penentuan konteks penyewa:
- Kenal pasti penyewa daripada permintaan masuk (subdomain atau token).
- Sahkan bahawa penyewa itu wujud dan aktif.
- Wariskan identiti itu supaya setiap kebergantungan hiliran boleh membacanya.
Pelajaran ini membina pipeline tersebut langkah demi langkah.
Dua Strategi Penentuan
Terdapat dua cara biasa untuk mengetahui penyewa bagi sesuatu permintaan:
- Berasaskan subdomain:
acme.app.comdipetakan kepada penyewaacme. Baca daripada pengepalaHost. Sesuai untuk sesi pelayar dan penjenamaan mengikut penyewa. - Berasaskan token: JWT membawa
tenant_id(selalunya sebagai tuntutan). Sesuai untuk klien API dan aplikasi mudah alih yang tiada subdomain.
Sistem matang menyokong kedua-duanya, dengan peraturan keutamaan yang jelas. Pilihan biasa: percayai tuntutan token dahulu (kerana ia ditandatangani), kemudian gunakan subdomain sebagai sandaran. Apa jua pilihan anda, dokumentasikan dan kuatkuasakannya secara konsisten.
Menghuraikan Penyewa daripada Subdomain
Subdomain datang daripada pengepala Host. Kami membuang domain asas yang diketahui dan mengambil label paling kiri. Label terpelihara seperti www dan api mesti ditolak supaya tidak pernah dianggap sebagai penyewa.
Pembantu ini hanya menggunakan logik rentetan, jadi mudah diuji secara unit secara berasingan:
BASE_DOMAIN = "app.com"
RESERVED = {"www", "api", "admin", ""}
def tenant_from_host(host: str):
# Strip port if present: 'acme.app.com:8000' -> 'acme.app.com'
host = host.split(":")[0].lower().strip()
if not host.endswith(BASE_DOMAIN):
return None
prefix = host[: -len(BASE_DOMAIN)].rstrip(".")
if not prefix:
return None
label = prefix.split(".")[0]
if label in RESERVED:
return None
return label
for h in ["acme.app.com:8000", "www.app.com", "app.com", "globex.eu.app.com"]:
print(h, "->", tenant_from_host(h))Mengekstrak Penyewa daripada Tuntutan JWT
Bagi klien API, identiti penyewa dibawa dalam token akses sebagai tuntutan yang ditandatangani. Selepas mengesahkan tandatangan, anda membaca tuntutan tenant_id.
Oleh sebab token itu ditandatangani, nilai ini boleh dipercayai dan biasanya harus mengatasi subdomain. Petikan di bawah menunjukkan bentuk penyahkodan dan pembacaan (pengesahan ditunjukkan sebagai ilustrasi; dalam pengeluaran, gunakan rahsia dan algoritma sebenar anda):
import base64
import json
def read_unverified_claims(token: str) -> dict:
# A JWT is header.payload.signature, each base64url-encoded.
payload_b64 = token.split(".")[1]
padding = "=" * (-len(payload_b64) % 4)
raw = base64.urlsafe_b64decode(payload_b64 + padding)
return json.loads(raw)
# Demo payload: {"sub": "user-7", "tenant_id": "acme"}
demo = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c2VyLTciLCJ0ZW5hbnRfaWQiOiJhY21lIn0.sig"
claims = read_unverified_claims(demo)
print("tenant_id:", claims.get("tenant_id"))Menyimpan Konteks dengan contextvars
Selepas diselesaikan, penyewa mesti boleh dicapai di mana-mana sahaja dalam permintaan tanpa menghantarnya melalui setiap argumen fungsi. contextvars Python ialah alat yang sesuai: setiap permintaan mendapat nilainya sendiri yang terasing, dan ia berfungsi dengan betul di bawah konkurensi asyncio.
Kami membungkus ContextVar mentah dalam pengakses kecil yang akan menimbulkan ralat jika tiada penyewa ditetapkan, lalu menjadikan konteks yang hilang sebagai kegagalan awal yang jelas:
from contextvars import ContextVar
_current_tenant: ContextVar[str] = ContextVar("current_tenant")
def set_current_tenant(tenant_id: str) -> None:
_current_tenant.set(tenant_id)
def get_current_tenant() -> str:
try:
return _current_tenant.get()
except LookupError:
raise RuntimeError("No tenant in context")
set_current_tenant("acme")
print("active tenant:", get_current_tenant())Middleware Penyelesaian
Middleware ialah tempat yang sesuai untuk menyelesaikan penyewa: ia berjalan sebelum sebarang pengendali laluan dan melihat permintaan mentah. Di sini kami menggabungkan token dan subdomain dengan peraturan keutamaan, menetapkan pemboleh ubah konteks, serta menolak lebih awal trafik tanpa nama yang memerlukan penyewa.
Perhatikan try/finally yang menetapkan semula token pemboleh ubah konteks supaya konteks tidak tertinggal antara permintaan pada pekerja yang sama:
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.responses import JSONResponse
class TenantMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request, call_next):
tenant = resolve_from_token(request) or tenant_from_host(
request.headers.get("host", "")
)
if tenant is None:
return JSONResponse({"detail": "Tenant not identified"}, status_code=400)
token = _current_tenant.set(tenant)
try:
request.state.tenant_id = tenant
return await call_next(request)
finally:
_current_tenant.reset(token)
# app.add_middleware(TenantMiddleware)Mengesahkan Kewujudan Penyewa
Menyelesaikan pengecam tidak sama dengan mempercayainya. Permintaan mungkin membawa ghost.app.com untuk penyewa yang telah dipadamkan atau digantung. Sebelum melakukan kerja sebenar, anda mesti mengesahkan perkara berikut:
- Penyewa wujud dalam daftar anda.
- Penyewa itu aktif (tidak digantung dan tidak mempunyai bayaran tertunggak jika anda mengehadkan akses berdasarkan pengebilan).
Selesaikan identiti dalam middleware, tetapi lakukan carian pangkalan data dalam kebergantungan supaya hasilnya boleh dicache bagi setiap permintaan dan digunakan semula. Kembalikan 404 untuk penyewa yang tidak diketahui dan 403 untuk penyewa yang digantung, supaya anda tidak mendedahkan penyewa yang wujud.
Mendedahkan Penyewa melalui Kebergantungan
Pengendali laluan tidak sepatutnya membaca pemboleh ubah konteks secara terus. Sebaliknya, dedahkan kebergantungan FastAPI yang mengembalikan rekod penyewa yang telah disahkan. Ini memberikan satu tempat untuk menguatkuasakan kewujudan dan status aktif, serta mendokumenkan keperluan itu dalam tandatangan laluan.
Kebergantungan itu membaca id yang ditetapkan oleh middleware, memuatkan penyewa, dan menimbulkan ralat HTTP yang sesuai jika sebaliknya:
from fastapi import Depends, HTTPException
async def get_tenant(tenant_id: str = Depends(get_current_tenant)):
tenant = await tenant_repo.find_by_slug(tenant_id)
if tenant is None:
raise HTTPException(status_code=404, detail="Unknown tenant")
if not tenant.is_active:
raise HTTPException(status_code=403, detail="Tenant suspended")
return tenant
@router.get("/projects")
async def list_projects(tenant=Depends(get_tenant)):
return await project_repo.list_for(tenant.id)Menyebarkan Konteks ke Pangkalan Data
Manfaat terbesar daripada satu sumber kebenaran untuk penyewa ialah skop data secara automatik. Dua corak yang biasa digunakan:
- Penapisan pada peringkat aplikasi: setiap panggilan repositori menambahkan
WHERE tenant_id = :tid, dengan membaca id daripadaget_current_tenant(). - Postgres Row-Level Security (RLS): tetapkan pemboleh ubah sesi bagi setiap permintaan, dan dasar menguatkuasakan pengasingan dalam pangkalan data itu sendiri.
Dengan RLS, anda menjalankan SET app.tenant_id pada permulaan penggunaan setiap sambungan, supaya penapis yang terlupa sekalipun tidak dapat merentas penyewa:
from contextlib import asynccontextmanager
@asynccontextmanager
async def tenant_scoped_session(session_factory):
tenant_id = get_current_tenant()
async with session_factory() as session:
# Bind tenant for the lifetime of this connection's RLS policies.
await session.execute(
text("SET app.tenant_id = :tid"), {"tid": tenant_id}
)
yield sessionTugas Latar Belakang Kehilangan Konteks
Perangkap yang sukar dikesan: contextvars terikat pada konteks pelaksanaan semasa. Kod yang berjalan selepas respons (tugas Celery, asyncio.create_task yang dimulakan tanpa konteks, atau tugas kumpulan benang) tidak akan melihat penyewa melainkan anda menghantarnya secara jelas.
Peraturan umum: tangkap id penyewa semasa masih berada dalam permintaan, kemudian serahkannya kepada unit kerja latar belakang sebagai argumen yang jelas. Jangan sekali-kali bergantung pada pemboleh ubah konteks untuk terus wujud selepas sempadan permintaan.
def enqueue_report(background_tasks, tenant=Depends(get_tenant)):
# Capture the id NOW; the worker runs outside this request's context.
tenant_id = tenant.id
background_tasks.add_task(build_report, tenant_id=tenant_id)
return {"status": "queued"}
async def build_report(tenant_id: str):
# Re-establish context inside the task before touching the DB.
set_current_tenant(tenant_id)
await generate(tenant_id)Susunan Middleware dan Pengujian
Susunan adalah penting. Middleware penyewa mesti berjalan sebelum kebenaran dan pengelogan supaya lapisan tersebut sudah dapat melihat penyewa. Dalam Starlette/FastAPI, middleware yang ditambahkan terakhir berjalan terlebih dahulu (ia membalut lapisan paling luar), jadi tambahkan penyelesaian penyewa selepas CORS tetapi pastikan ia mendahului pengesahan dalam pelaksanaan.
Untuk pengujian, hantar permintaan melalui timbunan middleware sebenar dan tegaskan pengasingan:
- Dua permintaan dengan subdomain yang berbeza tidak boleh melihat baris milik satu sama lain.
- Subdomain yang tidak diketahui mengembalikan
404; penyewa yang digantung mengembalikan403. Hostyang tiada dan token yang tiada mengembalikan400.
def test_subdomain_isolation(client):
r1 = client.get("/projects", headers={"Host": "acme.app.com"})
r2 = client.get("/projects", headers={"Host": "globex.app.com"})
assert r1.status_code == 200 and r2.status_code == 200
assert r1.json() != r2.json()
def test_unknown_tenant_404(client):
r = client.get("/projects", headers={"Host": "ghost.app.com"})
assert r.status_code == 404Semakan Pantas
Uji pemahaman anda tentang penyebaran konteks sepanjang kitar hayat permintaan.
Imbas Kembali
Anda telah membina saluran paip konteks penyewa yang lengkap untuk FastAPI berbilang penyewa:
- Selesaikan penyewa daripada tuntutan JWT (diutamakan dan ditandatangani) atau subdomain dalam pengepala
Host, dengan peraturan keutamaan yang jelas. - Simpan penyewa dalam
contextvaryang ditetapkan oleh middleware, dan tetapkan semula dalam blokfinallysupaya ia tidak pernah bocor antara permintaan. - Sahkan kewujudan dan status aktif dalam kebergantungan, dengan mengembalikan
404dan403secara tepat. - Sebarkan konteks ke pangkalan data melalui penapis bagi setiap panggilan atau pemboleh ubah sesi RLS Postgres sebagai pertahanan berlapis.
- Berwaspada terhadap tugas latar belakang dan benang: hantarkan id penyewa secara jelas dan wujudkan semula konteks, kerana contextvars tidak kekal selepas sempadan permintaan.
Satu sumber kebenaran untuk penyewa, yang dikuatkuasakan pada setiap lapisan, ialah perkara yang menghalang SaaS daripada membocorkan data.
Pelajari Kem Intensif Pembangunan Bahagian Belakang FastAPI dengan tutor kecerdasan buatan — percuma
Tulis dan jalankan kod sebenar dalam pelayar anda, dapatkan bantuan segera daripada tutor kecerdasan buatan yang tersedia 24/7, dan sambung semula dari tempat anda berhenti di web atau dalam aplikasi.
- Kursus
- 21
- Pelajaran
- 84
Soalan Lazim
Adakah pelajaran “Penyelesaian Konteks Penyewa dan Middleware” percuma?
Ya — sebanyak 3 pelajaran dalam laluan pembelajaran Kem Intensif Pembangunan Bahagian Belakang FastAPI, termasuk “Penyelesaian Konteks Penyewa dan Middleware”, boleh dibaca sepenuhnya secara percuma di web ini. Selepas itu, CoddyKit PRO membuka akses kepada semua pelajaran, serta latihan interaktif dengan penyunting kod terbina dalam dan tutor kecerdasan buatan yang tersedia 24/7. Kursus Kem Intensif Pembangunan Bahagian Belakang FastAPI merangkumi sejumlah 4 pelajaran.
Apakah yang akan saya pelajari dalam “Penyelesaian Konteks Penyewa dan Middleware”?
Kenal pasti penyewa semasa daripada subdomain atau token dan sebarkan konteks melalui setiap kebergantungan. Anda berlatih Kem Intensif Pembangunan Bahagian Belakang FastAPI menggunakan kod praktikal yang dijalankan terus dalam pelayar, manakala tutor kecerdasan buatan 24/7 menjawab soalan anda semasa anda mengikuti pelajaran.
Adakah saya memerlukan pengalaman untuk memulakan Kem Intensif Pembangunan Bahagian Belakang FastAPI?
Tiada pengalaman terdahulu diperlukan. Pembelajaran Kem Intensif Pembangunan Bahagian Belakang FastAPI di CoddyKit disusun untuk pelajar daripada peringkat pemula hingga lanjutan, jadi anda boleh bermula di sini atau dari awal dan belajar mengikut kadar anda sendiri. Ini ialah pelajaran 2 daripada 4.
Berapa lamakah pelajaran “Penyelesaian Konteks Penyewa dan Middleware” diambil?
Kebanyakan pelajaran CoddyKit mengambil masa kira-kira 5–10 minit. Setiap pelajaran ringkas dan interaktif, jadi anda boleh membuat kemajuan secara berterusan dan menyambung tepat dari tempat anda berhenti di web atau aplikasi.
Bolehkah saya menulis dan menjalankan kod dalam pelajaran Kem Intensif Pembangunan Bahagian Belakang FastAPI ini?
Ya. Setiap pelajaran Kem Intensif Pembangunan Bahagian Belakang FastAPI menyertakan penyunting kod terbina dalam, jadi anda boleh menulis dan menjalankan kod sebenar terus dalam pelayar serta menerima maklum balas kecerdasan buatan serta-merta — tanpa memerlukan persediaan setempat.
Semua pelajaran dalam kursus ini
- Strategi Pengasingan Penyewa dan Implikasinya
- Penyelesaian Konteks Penyewa dan Middleware
- Keselamatan Tahap Baris dan Pembahagian Data
- Pengukuran Penggunaan, Kuota dan Cangkuk Pengebilan