Ga direct naar de inhoud
DedicatedPHP Contact

Multitenant data-isolatie in PHP zonder datalekken

Ontwerp een PHP-SaaS die toegang tussen organisaties voorkomt met expliciete context, controles in data, queues, cache en tests.

Redactioneel diagram van een PHP-SaaS met geïsoleerde organisaties in database, cache, bestanden en queues

Multitenant data-isolatie in PHP wordt niet opgelost door een voorwaarde WHERE organization_id = ? toe te voegen aan het hoofdscherm. Een lek kan ontstaan in een API, een export, een cache, een bijlage, een queue consumer of een geplande taak. Het kan ook optreden wanneer een legitieme beheerder van organisatie wisselt en het systeem een eerdere context behoudt.

Het architecturale doel moet helder zijn: geen enkele bewerking die klantinformatie leest, wijzigt, verwerkt of levert, mag kunnen handelen zonder een verifieerbare organisatiecontext. Die context moet expliciet worden doorgegeven en gevalideerd op elke relevante grens van de applicatie.

Wat een SaaS-applicatie moet isoleren

Wat een SaaS-applicatie moet isoleren — guía visual de DedicatedPHP

Het transactionele model is slechts een deel van het risico-oppervlak. Inventariseer de middelen die aan een organisatie toebehoren en definieer voor elk ervan hoe de organisatietoewijzing wordt geïdentificeerd, opgeslagen, opgehaald, verwijderd en geaudit.

  • Transactionele gegevens: gebruikers, projecten, bestellingen, facturen, configuraties en relaties tussen entiteiten.
  • Bestanden en bijlagen: objecten in externe opslag, thumbnails, gegenereerde documenten en de bijbehorende metadata.
  • Cache: queryresultaten, berekende machtigingen, sessies, API-antwoorden en configuratiegegevens.
  • Zoekindexen: geïndexeerde documenten, suggesties en vooraf geaggregeerde filters.
  • Asynchrone verwerking: queue jobs, retries, importbatches en notificaties.
  • Operations en observability: logs, traces, metrics, supportexports en interne tools.

Niet alle middelen vereisen dezelfde strategie. Een openbare catalogus kan gedeeld zijn, terwijl een factuur, de PDF ervan en downloadlogs de ondubbelzinnige koppeling met de organisatie moeten behouden. De beslissing moet worden gedocumenteerd om te voorkomen dat een nieuwe entiteit zonder eigendomsregels ontstaat.

Het model voor data-isolatie kiezen

Er zijn drie gebruikelijke modellen. Er is geen model dat universeel beter is: de keuze hangt af van wettelijke vereisten, volume, operations, commercieel model en het vermogen van het team om het platform te onderhouden.

Gedeelde database met organisatiesleutel

Alle organisaties delen tabellen en elk record dat aan isolatie onderhevig is bevat een sleutel zoals organization_id. Dit is de meest directe aanpak om het product verder te ontwikkelen en globale geaggregeerde queries uit te voeren. Daartegenover staat dat deze aanpak extreme discipline vereist: elke query, relatie, index, cache en taak moet de organisatiegrens respecteren.

Gebruik minimaal foreign keys waar van toepassing, samengestelde indexen die beginnen met organization_id en eveneens samengestelde unieke constraints. Een bestelcode die bijvoorbeeld uniek is binnen een organisatie, mag niet als globaal uniek worden gedeclareerd als dat niet de bedrijfsregel is.

Gescheiden schema per organisatie

Elke klant werkt in een afzonderlijk logisch schema binnen dezelfde databaseserver. Dit verkleint het risico dat een filter in gescheiden tabellen wordt vergeten, maar maakt migraties, verbindingen, analysetools en globale queries complexer. Het is alleen geschikt als de database-engine, het framework en de dagelijkse operatie dat patroon consistent ondersteunen.

Database per organisatie

Gescheiden databases bieden een sterkere grens en kunnen restores of verplaatsingen van individuele klanten vereenvoudigen. Ze vergroten ook de inventaris van verbindingen, migraties, back-ups, monitoring en deployment van structuurwijzigingen. Het is raadzaam vooral te beoordelen hoe globale rapportages, massawijzigingen en herstel na fouten zullen worden uitgevoerd.

Fysieke scheiding vermindert bepaalde klassen van fouten, maar vervangt autorisatie, bestandscontrole, secretmanagement of validatie van context in gedeelde services niet.

Referentiearchitectuur: expliciete context aan de grenzen

De organisatiecontext mag niet worden afgeleid uit willekeurige parameters die door de browser worden verstuurd. Deze moet worden afgeleid uit een geauthenticeerde en geautoriseerde bron: een gevalideerd subdomein, een token met de juiste audience, een koppeling van de gebruiker met een organisatie of een integratiecredential die aan één organisatie is gekoppeld.

In een PHP-applicatie kan een invoerlaag een immutable contextobject opbouwen met de organisatie-ID, de actor, diens machtigingen en een request-ID. Controllers, consolecommando's en queue consumers ontvangen die context of bouwen die opnieuw op aan de hand van geverifieerde gegevens. Vermijd mutable globale variabelen die onterecht kunnen blijven bestaan in langlopende processen.

final class OrganizationContext {
    public function __construct(
        public readonly string $organizationId,
        public readonly string $actorId
    ) {}
}

Repositories moeten de context vereisen om geïsoleerde entiteiten te raadplegen of te wijzigen. Een interface die het moeilijk maakt om deze weg te laten, verdient de voorkeur boven een impliciete conventie die afhangt van het geheugen van elke ontwikkelaar. Pas waar mogelijk bovendien toegangsbeleid toe in de domeinlaag: tot een organisatie behoren autoriseert niet automatisch elke actie daarbinnen.

Vergeten filters in queries en relaties voorkomen

Een geïsoleerde query moet op organisatie filteren voordat er op bedrijfsidentificatoren wordt gezocht. Eerst een record ophalen op id en daarna de eigenaar controleren kan blootstellingen veroorzaken als het resultaat wordt geserialiseerd, gelogd of gebruikt voordat het wordt afgewezen.

  • Centraliseer queries in repositories of leesdiensten met methoden die de context ontvangen.
  • Verbied directe toegang tot geïsoleerde modellen vanuit controllers, templates en event consumers.
  • Controleer relaties: een lazy-loaded relatie kan het filter omzeilen dat op de hoofdentiteit is toegepast.
  • Gebruik databaseconstraints om relaties tussen rijen van verschillende organisaties te verhinderen wanneer het model dat toelaat.
  • Definieer conventies voor migraties, testseeds en analytische queries.

In database-engines die row-level security policies bieden, kunnen deze een aanvullende verdediging bieden. De invoering ervan moet echter verbindingstests, rollenbeheer en beoordeling van administratieve processen omvatten. Het is niet raadzaam aan te nemen dat een databasebeleid automatisch bestanden, cache of externe indexen beschermt.

Risico's buiten de hoofdwebflow

Opaque identifiers beperken enumeratie, maar autoriseren geen toegang. Een UUID of willekeurige identifier moet nog steeds worden opgelost binnen de actieve organisatie. Evenzo vereist een ondertekende download-URL een object dat aan de juiste organisatie is gekoppeld, een passende vervaltijd en intrekkingsregels wanneer machtigingen wijzigen.

Cache keys moeten de organisatie-ID bevatten en, wanneer de inhoud afhangt van machtigingen, een aanvullende dimensie voor rol of autorisatieversie. Een key zoals dashboard:summary is onveilig in een multitenantomgeving; een key met expliciete organisatiecontext maakt bovendien nauwkeurigere invalidaties mogelijk.

Exports zijn bijzonder gevoelig omdat ze gewoonlijk buiten het oorspronkelijke request worden uitgevoerd. Sla op wie deze heeft aangevraagd, voor welke organisatie, welke filters zijn goedgekeurd en waar het resultaat wordt afgeleverd. Verstuur geen bijlagen of links naar ontvangers die zijn berekend op basis van niet-gevalideerde gegevens.

De context doorgeven in API's, webhooks en queues

Een API moet de organisatie afleiden uit de credential of controleren of de aangevraagde resource behoort tot de organisatie die aan die credential is gekoppeld. Een header X-Organization-Id toestaan kan geldig zijn voor operators met expliciete delegatie, maar vereist specifieke autorisatie, auditing en een interface waarin een wijziging van organisatiecontext zichtbaar is.

Inkomende webhooks mogen niet vertrouwen op een organisatie-ID in de body zonder handtekening, afzender en eerdere koppeling van de integratie te verifiëren. Genereer voor uitgaande webhooks events op basis van al afgebakende gegevens en vermijd het hergebruiken van payloads uit een gedeelde queue zonder de ontvanger te valideren.

Elke asynchrone job moet naast de resource-ID een organisatie-ID meenemen en de context reconstrueren voordat een query wordt uitgevoerd. De consumer moet beide waarden controleren, zelfs als de job intern is aangemaakt. Retries, delayed jobs en geplande taken vereisen dezelfde regel: er is geen impliciete requestcontext die veilig beschikbaar is.

Verifieerbare tests en diagnostische signalen

De belangrijkste test is niet dat een organisatie haar eigen gegevens ziet, maar dat zij die van een andere niet kan lezen of wijzigen. Maak twee organisaties met bewust vergelijkbare gegevens en voer integratietests uit voor elk toegangspunt: webinterface, API, commando's, exports, downloads en queue consumers.

  • Vraag een resource van organisatie B op met een sessie of credential van organisatie A en verwacht een niet-onthullend antwoord.
  • Probeer resources van andere organisaties bij te werken, te verwijderen, te downloaden en te exporteren, en raadpleeg ze niet alleen.
  • Controleer dat de cache keys van A en B onafhankelijke resultaten opleveren.
  • Voer een queue job uit met een resource van een andere organisatie en verifieer dat deze op gecontroleerde wijze faalt.
  • Test restores, imports en nachtelijke taken met gegevens van meer dan één organisatie.
  • Log gevoelige acties met actor, organisatie, resource en resultaat, zonder onnodige persoonsgegevens in de logs op te nemen.

Property-based tests kunnen de handmatige gevallen aanvullen: voor elke resource die onder een organisatie is aangemaakt, mag geen actor zonder geldige koppeling met die organisatie deze via een blootgestelde route kunnen waarnemen of wijzigen. Deze eigenschap moet worden toegepast op toekomstige wijzigingen van endpoints en repositories.

Adoptieplan voor een bestaande applicatie

Als gegevens al gemengd zijn, begin dan niet met het herschrijven van de hele applicatie. Inventariseer eerst entiteiten, flows, integraties en administratieve toegangen. Definieer daarna het eigendom van elk record en los ambigue gevallen op met bedrijfsregels die kunnen worden herzien.

  1. Voeg de organisatie-entiteit en de koppelingssleutel naar de organisatie toe aan de doeltabellen.
  2. Vul die sleutel via een gecontroleerde migratie en bewaar bewijs van gevallen zonder betrouwbare toewijzing.
  3. Introduceer afgebakende repositories en tests voor organisatieoverschrijdende toegang op de gevoeligste routes.
  4. Neem de organisatiecontext op in cache, bestanden, zoekopdrachten en nieuwe jobs.
  5. Migreer oude flows geleidelijk en blokkeer nieuwe queries zonder context in code review.
  6. Activeer strengere controles wanneer metrics en tests voldoende dekking aantonen.

Beslissingen die u vóór groei moet documenteren

Beslissingen die u vóór groei moet documenteren — guía visual de DedicatedPHP

Documenteer vóór u de volgende organisatie toevoegt het gekozen model, de bron die bepalend is voor de context, uitzonderingen voor administratieve toegang, de identifierstrategie, de cachegrenzen, het eigendom van bestanden, dataherstel, logretentie en de procedure bij een vermoeden van organisatieoverschrijdende toegang.

Bepaal ook wie namens een andere organisatie mag handelen, hoe die delegatie wordt goedgekeurd en hoe deze wordt ingetrokken. Multitenant data-isolatie in PHP wordt onderhouden met expliciete beslissingen, herhaalbare technische constraints en tests die een architectuurbelofte omzetten in verifieerbaar gedrag.

Wil je deze ideeën toepassen op je project?Laten we uw PHP-platform bespreken.
Bekijk gerelateerde service