Zum Inhalt springen
DedicatedPHP Kontakt

Vertrauensgrenzen zwischen PHP-Modulen entwerfen, ohne Regeln zu duplizieren

Legen Sie fest, was jedes PHP-Modul voraussetzen darf, was es beim Empfang von Daten validieren muss und wie sich Autorisierung und Datenbesitz überprüfen lassen.

Konzeptionelles Diagramm von PHP-Modulen, die durch Verträge und Autorisierungskontrollen getrennt sind

In einer modularen PHP-Anwendung ist eine Grenze nicht nur eine Trennung von Ordnern, Klassen oder Namespaces. Sie bestimmt auch, welche Daten ein Modul annehmen darf, welche Operationen es ausführen kann und wer dafür verantwortlich ist, die jeweiligen Bedingungen zu prüfen. Bleiben diese Entscheidungen implizit, vertrauen Module einander möglicherweise zu sehr, duplizieren Validierungen oder legen Daten offen, über die sie nicht verfügen sollten.

Vertrauensgrenzen zwischen PHP-Modulen zu entwerfen bedeutet, diese Verantwortlichkeiten explizit zu machen und sie bei Änderungen am System überprüfbar zu halten. Das heißt weder, jeden internen Aufruf wie nicht vertrauenswürdigen Datenverkehr zu behandeln, noch, alle Regeln in jeder Schicht zu wiederholen. Vielmehr geht es darum, zu erkennen, wo sich die Zuständigkeit ändert, klare Verträge festzulegen und jede Operation in dem Modul zu validieren, das zu ihrer Ausführung befugt ist.

Warum eine Grenze zwischen Modulen auch eine Vertrauensgrenze ist

Warum eine Grenze zwischen Modulen auch eine Vertrauensgrenze ist — guía visual de DedicatedPHP

Ein Modul kann Daten aus einem anderen über eine interne Schnittstelle, ein Ereignis, eine API oder eine gemeinsame Abfrage beziehen. In all diesen Fällen sollte geklärt werden, welche Garantien der Absender bietet und welche der Empfänger überprüfen muss. Der Speicherort des Codes reicht als Antwort nicht aus: Eine direkt aufgerufene PHP-Methode kann Daten erhalten, die aus einer externen Anfrage, einem Hintergrundprozess oder einem anderen, unabhängig weiterentwickelten Modul stammen.

Unterscheiden Sie zwischen Formatvalidierung, Geschäftsinvarianten und Autorisierung. Bei der Formatvalidierung wird geprüft, ob eine Kennung oder ein Datum eine zulässige Darstellung hat. Eine Invariante stellt beispielsweise sicher, dass eine Operation keinen negativen Betrag enthält. Die Autorisierung bestimmt, ob der Akteur diese Operation an dieser Ressource ausführen darf. Diese Kontrollen hängen zusammen, sind aber nicht austauschbar.

Eine Vertrauensgrenze entsteht insbesondere dann, wenn sich der Akteur, die Zuständigkeit für die Daten oder der Eingangskanal ändert. So kann ein Abrechnungsmodul zwar einen Auftrag von einem anderen Modul erhalten, sollte daraus aber nicht ableiten, dass die Person, die die Anfrage ausgelöst hat, jede beliebige Rechnung ändern darf. Identität und Kontext müssen explizit und überprüfbar übermittelt werden.

Die Daten und Operationen erfassen, die jede Grenze überschreiten

Beginnen Sie damit, die relevanten Interaktionen zu erfassen, statt die gesamte Anwendung neu zu entwerfen. Dokumentieren Sie für jede Grenze, wer den Aufruf tätigt, wem die Operation gehört, welche Daten ausgetauscht werden und ob der Aufruf synchron oder asynchron erfolgt. Berücksichtigen Sie auch weniger sichtbare Wege: Konsolenbefehle, geplante Prozesse, Queue-Consumer und Verwaltungsaufgaben.

  • Daten: Erfassen Sie die übertragenen Felder, ihre Herkunft und ob sie personenbezogene, sensible oder zu einer anderen Domäne gehörende Informationen enthalten.
  • Operationen: Halten Sie fest, welche Änderungen angefordert werden und welches Modul sie ausführen muss.
  • Kontext: Bestimmen Sie, welcher Akteur, welche Organisation, welcher Tenant oder welcher Zweck die Anfrage begleiten muss.
  • Ergebnisse: Legen Sie fest, welche Informationen das Modul zurückgeben und welche Fehler es mitteilen darf.

Diese Bestandsaufnahme hilft, Risikosignale zu erkennen: Ein Modul fragt fremde Tabellen ab, um eine Schnittstelle zu umgehen; es erhält eine Ressourcenkennung, ohne deren Zugehörigkeit zu prüfen; oder es akzeptiert ein veränderliches Domänenobjekt, das von einem anderen Modul erstellt wurde. Sie macht auch Duplizierungen sichtbar: Mehrere Verbraucher setzen ihre eigene Auslegung einer Regel um, für die eigentlich eine einzige Domäne zuständig ist.

Explizite Verträge definieren: Format, Invarianten und Kompatibilität

Ein Vertrag beschreibt, was der Empfänger zur Ausführung einer Operation benötigt und welche Garantien er bei der Antwort bietet. In PHP kann er je nach Architektur durch unveränderliche DTOs, Interfaces, klar definierte Typen oder Nachrichtenschemas ausgedrückt werden. Die konkrete Form ist weniger wichtig als die Klarheit: Vermeiden Sie es, vollständige Entitäten zu übergeben, wenn der Verbraucher nur wenige Werte benötigt.

Der Vertrag muss Pflichtfelder, Grenzen und Bedeutungen präzisieren. Ein customerId sagt beispielsweise für sich genommen nicht aus, ob damit ein globaler Kunde, ein Kunde einer bestimmten Organisation oder ein für einen bestimmten Benutzer sichtbarer Kunde gemeint ist. Wenn der Kontext für die Operation relevant ist, nehmen Sie ihn auf oder legen Sie fest, wie das zuständige Modul ihn ermittelt.

Vereinbaren Sie außerdem unterscheidbare Fehler. Eine fehlerhaft formatierte Eingabe ist nicht dasselbe wie eine nicht autorisierte Operation, eine nicht vorhandene Ressource oder ein Konflikt mit dem aktuellen Zustand. Die Details, die dem Aufrufer mitgeteilt werden, müssen ausreichen, um das Ergebnis zu verarbeiten, ohne Informationen preiszugeben, die das Aufzählen von Ressourcen oder das Ermitteln von Daten Dritter erleichtern.

Verträge sollten mit bewusst geplanter Kompatibilität weiterentwickelt werden. Ein optionales Feld hinzuzufügen kann weniger störend sein, als die Bedeutung eines bestehenden Feldes zu ändern. Trotzdem müssen dabei Produzenten und Verbraucher überprüft werden. Berücksichtigen Sie bei asynchronen Nachrichten, dass eine ältere Version auch nach dem Deployment neuen Codes noch im Umlauf sein kann. Verwechseln Sie das Deployment des Codes nicht mit der Aktivierung eines neuen Verhaltens: Verwenden Sie einen kontrollierten Übergang, wenn Produzenten und Verbraucher nicht gleichzeitig geändert werden.

Autorisierung und Zugehörigkeit dort prüfen, wo die Operation ausgeführt wird

Die syntaktische Validierung kann nahe am Eingang stattfinden, die Autorisierung muss jedoch in dem Kontext geprüft werden, der die Ressource kennt und die Aktion steuert. Das Modul, dem beispielsweise eine Rechnung gehört, verfügt über die nötigen Informationen, um zu entscheiden, ob sie ausgestellt oder geändert werden darf. Diese Entscheidung sollte es nicht an ein aufrufendes Modul delegieren, das nur die Identität des Benutzers kennt.

Eine sinnvolle Prüfung verknüpft Akteur, Aktion, Ressource und Kontext: Wer fordert welche Operation an, für welche Ressource und innerhalb welcher Organisation oder welchen Tenants? Gehen Sie nicht davon aus, dass eine übermittelte Kennung eine Berechtigung darstellt. Prüfen Sie die Zugehörigkeit an der Grenze, an der die Operation ausgeführt wird, auch wenn die Anfrage aus internem, als vertrauenswürdig geltendem Code kommt. So wird verhindert, dass ein alternativer Pfad oder eine spätere Architekturänderung eine Autorisierung umgeht, die ausschließlich in der Weboberfläche erfolgt.

Vermeiden Sie jedoch, die vollständige Richtlinie in allen Modulen zu duplizieren. Zentralisieren Sie die Entscheidung im zuständigen Modul und stellen Sie eine Operation oder einen Dienst mit einem klaren Vertrag bereit. Verbrauchende Module können zusätzliche Prüfungen zur Verbesserung der Benutzererfahrung vornehmen, dürfen aber nicht zur einzigen Sicherheitsschranke werden.

Verhindern, dass gemeinsam genutzte Entitäten privilegierten Zugriff ermöglichen

ORM-Entitäten zwischen Modulen gemeinsam zu nutzen, scheint praktisch, kann aber versteckte Abhängigkeiten schaffen. Wenn mehrere Module dieselbe Entität laden und ändern, kann jedes davon die Operationen umgehen, die ihre Invarianten schützen sollen. Außerdem wirken sich Änderungen am Schema oder Verhalten auf Verbraucher aus, die das Modell nicht kontrollieren.

Übertragen Sie stattdessen die benötigten Daten über Commands, Queries oder DTOs. Das zuständige Modul bleibt für die Persistierung und Validierung seiner Invarianten verantwortlich; der Verbraucher benötigt keinen allgemeinen Zugriff auf dessen Repositories oder Tabellen. Ist ein Lesezugriff erforderlich, entwerfen Sie eine Abfrage, die nur die minimal erforderlichen Daten zurückgibt und die festgelegten Berechtigungen und den Geltungsbereich berücksichtigt.

Eine gemeinsame Datenbank bedeutet nicht, dass auch die Zuständigkeit geteilt werden muss. Noch bevor Daten physisch getrennt werden, lässt sich festlegen, dass nur ein Modul in bestimmte Tabellen schreibt und die anderen dokumentierte Schnittstellen verwenden. Diese Regel verringert den Aufwand einer späteren Trennung und erleichtert das Erkennen von Zugriffen außerhalb des Vertrags.

Prüfen, ob die Isolation bei Änderungen am System erhalten bleibt

Tests sollten sowohl den gültigen Ablauf als auch Versuche abdecken, eine Grenze ohne Berechtigung zu überschreiten. Unit-Tests prüfen die Regeln des zuständigen Moduls; Integrationstests überprüfen Verträge, Persistierung und Autorisierungskonfiguration zwischen Komponenten. Nehmen Sie Fälle auf, in denen ein gültiger Akteur versucht, auf eine Ressource eines anderen Tenants zuzugreifen, sowie Fälle mit einer nicht vorhandenen Ressource und unvollständigem Kontext.

  • Prüfen Sie, dass eine gültige Kennung allein keinen Zugriff gewährt.
  • Stellen Sie sicher, dass Autorisierungsfehler keine Daten der geschützten Ressource zurückgeben.
  • Testen Sie, dass Verbraucher Invarianten nicht über gemeinsam genutzte Entitäten oder den direkten Zugriff auf Repositories ändern können.
  • Führen Sie Vertragstests aus, wenn Produzenten und Verbraucher unabhängig voneinander deployed werden.

Die Tests müssen die tatsächliche Richtlinie abbilden und dürfen nicht nur prüfen, ob eine Exception ausgelöst wird. Stellen Sie sicher, dass die Änderung nicht persistiert, kein unzulässiges Ereignis veröffentlicht und bei einer Wiederholung der Operation keine Teilauswirkungen erzeugt werden. Legen Sie bei mehrstufigen Abläufen fest, was bei einem Fehler in einer Phase geschieht und wie der Zustand abgeglichen wird.

Grenzen überprüfen, wenn ein Dienst ausgegliedert oder die Zuständigkeit geändert wird

Grenzen überprüfen, wenn ein Dienst ausgegliedert oder die Zuständigkeit geändert wird — guía visual de DedicatedPHP

Bei der Ausgliederung in einen separaten Dienst werden Annahmen sichtbar, die zuvor im Speicher verborgen waren: Identität, Berechtigungen, Latenz, Wiederholungsversuche, Duplikate und Verfügbarkeit. Der Vertrag muss genügend Kontext übertragen und Netzwerkfehler berücksichtigen. Wandeln Sie einen Remote-Aufruf nicht in eine bloße Kopie eines lokalen Aufrufs um, ohne Zeitlimits, Idempotenz und Fehlerverhalten zu überprüfen.

Überprüfen Sie die Grenzen auch dann, wenn sich die Zuständigkeit für Daten ändert, ein Tenant hinzukommt, ein neuer Eingangskanal eingeführt wird oder neue Verbraucher entstehen. Fragen Sie, wer schreiben und lesen darf, welches Modul über die Autorisierung entscheidet und welche Garantien während der Migration bestehen bleiben. Halten Sie diese Antworten zusammen mit der Vertragsdokumentation und den entsprechenden Tests fest.

Eine sinnvolle Vertrauensgrenze hängt weder davon ab, dass alle Module im selben Prozess bleiben, noch davon, dass sich das Team an informelle Konventionen erinnert. Sie zeichnet sich dadurch aus, dass die Zuständigkeit zugewiesen ist, die grenzüberschreitenden Daten begrenzt sind, Berechtigungen dort geprüft werden, wo die Operation ausgeführt wird, und Tests unzulässige Zugriffe erkennen. So bleibt die Isolation geschützt, ohne jedes Modul zu einer Kopie der Regeln der anderen zu machen.

Möchten Sie diese Ideen in Ihrem Projekt anwenden?Lass uns über deine PHP-Plattform sprechen.
Verwandten Dienst anzeigen