Sichere Multi-tenancy in Lino
Multi-tenancy ermöglicht es einer einzigen Anwendung, mehrere Kunden, Unternehmen oder Organisationen zu bedienen und dabei Daten und Berechtigungen isoliert zu halten. In Lino wird Tenant als Teil des Anwendungsflusses behandelt: Das Feature erzeugt die tenancy-Grundlage, löst den Tenant über Subdomain, Host oder Slug über ITenantContext auf und stellt den durch das JWT authentifizierten Benutzer über IUserContext bereit.
Das JWT enthält auch den TenantId, für den der Benutzer authentifiziert wurde. Lino vergleicht diesen Tenant mit dem für die Anfrage aufgelösten Tenant. Deshalb konzentriert sich diese Seite darauf, was Sie entscheiden müssen, nachdem diese Grundlage existiert: Entitätsmodellierung, Berechtigungen pro Organisation und korrekte Verwendung der generierten Abstraktionen.
Tenant-Support hinzufügen
Verwenden Sie das Tenant-Feature, wenn das System SaaS ist oder wenn Daten verschiedener Kunden in derselben Anwendung koexistieren müssen, ohne zwischen Kontexten zu leaken.
lino feature tenant add
Der Assistent erzeugt die Grundlage für Tenant-Registrierung, Benutzerzuordnung, Auflösung des aktuellen Tenant, Weitergabe über den Anwendungskontext und Integration mit Authentifizierung. Der ITenantContext repräsentiert den Tenant, der über Subdomain, Host oder Slug aufgelöst wurde, während der IUserContext den durch das JWT authentifizierten Benutzer repräsentiert, einschließlich des TenantId, für den er authentifiziert wurde.
Nach der Generierung prüfen Sie, welche Entitäten wirklich zu einem Tenant gehören und welche global sind. Diese Prüfung ist Teil der Produktmodellierung: multi-tenancy bedeutet nicht nur, eine Tenants-Tabelle zu erstellen, sondern zu entscheiden, welche Regeln, Screens, Berechtigungen, Daten und Operationen vom aktuellen, durch Lino validierten Tenant abhängen.
- Tenant-scoped: Bestellungen, Produkte eines Kunden, Unternehmenseinstellungen, Kunden eines CRM oder alle Daten, die zu einer bestimmten Organisation gehören.
- Global: öffentlicher Plattformkatalog, Pläne, interne Betriebsressourcen, administrative Einstellungen oder Daten, die vor der Auswahl eines Tenant verwendet werden.
- Hybrid: globale Daten mit Anpassung pro Tenant, die explizite Modellierung erfordern, um falsche Duplikation oder Regellecks zu vermeiden.
In einem modularen Monolithen ist normalerweise das tenancy-Modul Eigentümer der ursprünglichen Tenant-Entität. Andere Module, wie Katalog oder CRM, sollten nicht direkt auf diese Entität zugreifen. Wenn sie Tenant kennen müssen, können sie shadow entities mit den minimal notwendigen Feldern halten, gespeist durch Integration Events wie Erstellung oder Aktualisierung eines Tenant.
Tenant context
Mit dem Tenant-Feature zentralisiert Lino die Auflösung des aktuellen Tenant und stellt diesen Wert über ITenantContext bereit. Dieser Kontext repräsentiert den Tenant, der aus Subdomain, Host oder Slug der Anfrage aufgelöst wurde, einschließlich Informationen wie TenantId und Slug, wenn der Tenant gefunden wurde.
In authentifizierten Flows liest IUserContext den Benutzer aus dem JWT. Dieses JWT enthält auch den TenantId, für den der Benutzer authentifiziert wurde. Lino vergleicht den Tenant aus IUserContext mit dem Tenant aus ITenantContext und verhindert, dass Geschäftsregeln divergierenden Kontexten vertrauen.
In generierten oder später hinzugefügten Features bevorzugen Sie immer diese Abstraktionen, statt claims manuell erneut zu lesen oder in jedem handler einen beliebigen TenantId zu empfangen. So verwenden commands, queries, Seiten und Integrationen dieselbe Source of Truth und die bereits von Lino implementierten Sicherheitsmechanismen bleiben erhalten.
Background Jobs, Event-Consumer und Integrationen benötigen ebenfalls Kontext. Ein job, der tenant-scoped Produkte, Kunden oder Benutzer verarbeitet, muss explizit erhalten, welcher Tenant verarbeitet wird, weil es keine aktive HTTP-Anfrage gibt, aus der ITenantContext und IUserContext automatisch abgeleitet werden könnten.
Tenant-scoped Entitäten
Tenant-scoped Entitäten tragen den Tenant-Identifier und nehmen an Query-Filtern teil. Dadurch muss der übliche Flow nicht manuell daran denken, TenantId in jeder query anzuwenden; das Feature sollte den validierten Kontext und die von Lino generierten Filter verwenden.
- Nehmen Sie Tenant in aggregates auf, die zu einer bestimmten Organisation gehören.
- Konfigurieren Sie Indizes unter Berücksichtigung von Tenant und häufig abgefragten Feldern.
- Verwenden Sie
ITenantContextundIUserContextin commands und queries, statt Tenant als isolierte Autorität zu empfangen. - Definieren Sie in imports und jobs explizit, welcher Tenant verarbeitet wird.
Eine tenant-scoped Entität gehört zu einem Tenant. Wenn eine Entität zu mehreren Tenants zu gehören scheint, behandeln Sie das als Modellierungsentscheidung: vielleicht ist sie global, vielleicht sollte es eine Kopie pro Tenant geben, oder vielleicht fehlt eine Zwischenentität, die die Beziehung repräsentiert. Ein Produkt, Kunde oder Benutzer kann in verschiedenen Modulen auch unterschiedliche Bedeutung haben; in solchen Fällen kann eine shadow entity mit wenigen Feldern korrekter sein als das Teilen der ursprünglichen Entität.
Unterscheiden Sie auch Referenz von ownership. Ein Produkt im Katalog kann den Tenant referenzieren, aber das bedeutet nicht, dass der Katalog den Lifecycle des Tenant kontrolliert. Das tenancy-Modul bleibt Eigentümer von Erstellung, Aktivierung, Domain, Branding und Isolationsmodus; der Katalog hält nur die Daten, die zum Filtern und Validieren des eigenen Flows nötig sind.
- Indizes: kombinieren Sie
TenantIdmit Feldern, die für Suche, Sortierung und Eindeutigkeit pro Tenant verwendet werden. - Commands: wenden Sie die Operation im aktuellen validierten Tenant an, wenn die Entität tenant-scoped ist.
- Queries: halten Sie Paginierung, Filter und Zähler im selben von Lino validierten Kontext.
- Events: fügen Sie genug Kontext hinzu, damit Consumer Projektionen aktualisieren können, ohne den Producer unzulässig abzufragen.
Datenisolation
Isolation kann per Spalte, schema oder separater Datenbank erfolgen. Die Wahl hängt von Kosten, Volumen, vertraglichen Anforderungen und Betriebskapazität ab.
| Strategie | Vorteile | Wann verwenden |
|---|---|---|
| TenantId-Spalte | Einfacher und günstiger | Eine gute Anfangswahl für viele SaaS-Produkte, kombiniert mit Filtern und Lino-Kontext. |
| Schema pro Tenant | Mittlere Isolation | Nützlich, wenn höhere Trennungsanforderungen bestehen, ohne zu Datenbank pro Tenant zu wechseln. |
| Datenbank pro Tenant | Starke Isolation | Empfohlen, wenn Vertrag, Volumen oder Betrieb höhere Kosten und Automatisierung rechtfertigen. |
Für die meisten SaaS-Produkte in einer frühen Phase ist eine Spalte pro Tenant mit globalen Filtern und zentralisiertem Kontext ein pragmatischer Ausgangspunkt. Lino hilft mit dieser Grundlage; Ihre Prüfung sollte bestätigen, dass tenant-scoped aggregates den richtigen Tenant tragen und neue queries weiterhin den aktuellen, durch die generierten Abstraktionen validierten Tenant verwenden.
In modularen Services sind Tenant-Isolation und Modul-Isolation unterschiedliche Anliegen. Das Modul Catalog kann eine Produkttabelle mit TenantId haben; das Modul CRM kann Kunden mit TenantId haben; und das Modul Tenancy bleibt Eigentümer der ursprünglichen Tenant-Registrierung. Die Anwesenheit desselben Identifiers autorisiert keinen direkten Zugriff zwischen Modulen.
Roles und Berechtigungen pro Tenant
In SaaS kann ein Benutzer Administrator in einem Tenant und Operator in einem anderen sein. Lino erzeugt die Grundlage, um mit der Benutzer-Tenant-Verknüpfung zu arbeiten, und die Berechtigungen der Features müssen im aktiven, vom Framework validierten Kontext bewertet werden.
Es gibt auch administrative Berechtigungen außerhalb eines Tenant. Ein Plattform-Betriebsscreen kann nur im Systemkontext zugänglich sein, während Seiten wie Produkte und Kunden einen aktiven Tenant verlangen können. Diese Trennung macht klar, wann eine Aktion global ist und wann sie vom Tenant im ITenantContext und vom Benutzer im IUserContext abhängt.
- Modellieren Sie roles pro Tenant, wenn Berechtigungen je Organisation variieren.
- Verwenden Sie den aktuellen Kontext in Autorisierungsprüfungen für Seiten, commands und queries.
- Halten Sie claims kompakt und tragen Sie im token nur das, was am authentifizierten Flow teilnehmen muss.
- Widerrufen oder aktualisieren Sie tokens, wenn sich kritische Berechtigungen ändern.
| Kontext | Beispiel | Behandlung |
|---|---|---|
| System | Plattformadministration, Pläne, Tenants und globale Einstellungen. | Außerhalb des Scopes eines bestimmten Tenant ausführen. |
| Tenant | Produkte, Kunden, Bestellungen, Benutzer und roles dieses Tenant. | Den aktuellen, von Lino validierten Tenant verwenden. |
| Beide | Einige Identitäts- oder Verwaltungsfunktionen, die sowohl im System als auch im Tenant existieren. | Im Use Case deklarieren, welcher Kontext verwendet wird. |
JWT und Tenant-Sicherheit
Wenn das Tenant-Feature aktiv ist, verwendet Lino das JWT als Quelle für den Kontext des authentifizierten Benutzers. Der IUserContext liest die claims des Benutzers und den TenantId aus dem token, ohne das JWT in eine Kopie der Datenbank zu verwandeln.
- Halten Sie issuer, audience, Signatur und Ablaufzeit korrekt konfiguriert.
- Verwenden Sie den von Lino validierten Tenant, statt einen manuellen Wechsel nur auf Basis von header, Route oder Screen-Payload zu akzeptieren.
- Vermeiden Sie zu große claims; aufgeblähte tokens erschweren Betrieb und Erneuerung.
- Protokollieren Sie Zugriffe mit Benutzer, Tenant und Operation, wenn der Flow Auditing erfordert.
In Anwendungen mit Subdomains wie acme.dev.localhost und globex.dev.localhost repräsentiert der ITenantContext den für die Anfrage aufgelösten Tenant. Der IUserContext repräsentiert den authentifizierten Benutzer und den im JWT vorhandenen Tenant. Lino vergleicht diese beiden Kontexte, um zu verhindern, dass ein für einen Tenant ausgestellter token in einem anderen Tenant-Kontext verwendet wird.
Auch Autorisierung bleibt kontextuell. Die Berechtigung, Produkte anzuzeigen, gilt zum Beispiel innerhalb des Tenant, in dem sie erteilt wurde und für den der Benutzer authentifiziert wurde. Verwenden Sie beim Erstellen neuer Features die vorhandenen Berechtigungen und Kontexte, statt Abkürzungen zu erstellen, die einen losen TenantId erhalten und den authentifizierten Flow ignorieren.
