php titel Wie kann ich eine Access Control List in meiner Web MVC-Anwendung implementieren?




wordpress seo titel (3)

Erster Teil / Antwort (ACL-Implementierung)

Meiner bescheidenen Meinung nach wäre der beste Weg, dies zu erreichen, das Dekorationsmuster . Grundsätzlich bedeutet dies, dass du dein Objekt nimmst und es in ein anderes Objekt legst, das wie eine schützende Hülle wirkt. Dies würde NICHT erfordern, dass Sie die ursprüngliche Klasse erweitern. Hier ist ein Beispiel:

class SecureContainer
{

    protected $target = null;
    protected $acl = null;

    public function __construct( $target, $acl )
    {
        $this->target = $target;
        $this->acl = $acl;
    }

    public function __call( $method, $arguments )
    {
        if ( 
             method_exists( $this->target, $method )
          && $this->acl->isAllowed( get_class($this->target), $method )
        ){
            return call_user_func_array( 
                array( $this->target, $method ),
                $arguments
            );
        }
    }

}

Und so würdest du diese Art von Struktur verwenden:

// assuming that you have two objects already: $currentUser and $controller
$acl = new AccessControlList( $currentUser );

$controller = new SecureContainer( $controller, $acl );
// you can execute all the methods you had in previous controller 
// only now they will be checked against ACL
$controller->actionIndex();

Wie Sie vielleicht bemerken, hat diese Lösung mehrere Vorteile:

  1. Containment kann für jedes Objekt verwendet werden, nicht nur für Instanzen von Controller
  2. Die Prüfung auf Autorisierung erfolgt außerhalb des Zielobjekts, was bedeutet:
    • Originalobjekt ist nicht für die Zugriffskontrolle verantwortlich, hält sich an SRP
    • Wenn Sie "permission denied" erhalten, sind Sie nicht in einem Controller gesperrt, mehr Optionen
  3. Sie können diese geschützte Instanz in ein beliebiges anderes Objekt injizieren. Dadurch bleibt der Schutz erhalten
  4. wickle es ein und vergiss es ... du kannst so tun , als ob es das Originalobjekt wäre, es wird gleich reagieren

Aber auch bei dieser Methode gibt es ein großes Problem: Sie können nicht nativ prüfen, ob das gesicherte Objekt implementiert und die Schnittstelle ist (dies gilt auch für die Suche nach vorhandenen Methoden) oder ist Teil einer Vererbungskette.

Zweiter Teil / Antwort (RBAC für Objekte)

In diesem Fall sollten Sie als Hauptunterschied erkennen, dass Ihre Domain-Objekte (im Beispiel: Profile ) selbst Details zum Eigentümer enthalten. Das bedeutet, dass Sie überprüfen müssen, ob (und auf welcher Ebene) der Benutzer darauf zugreifen darf, Sie müssen diese Zeile ändern:

$this->acl->isAllowed( get_class($this->target), $method )

Im Wesentlichen haben Sie zwei Möglichkeiten:

  • Stellen Sie der ACL das fragliche Objekt bereit. Aber Sie müssen aufpassen, das Demeter-Gesetz nicht zu verletzen:

    $this->acl->isAllowed( get_class($this->target), $method )
    
  • Fordern Sie alle relevanten Details an und stellen Sie der ACL nur das zur Verfügung, was sie benötigt, was sie auch ein wenig mehr Unit-Testing-freundlich macht:

    $command = array( get_class($this->target), $method );
    /* -- snip -- */
    $this->acl->isAllowed( $this->target->getPermissions(), $command )
    

Einige Videos, die Ihnen helfen könnten, Ihre eigene Implementierung zu erstellen:

Seitliche Notizen

Sie scheinen das ziemlich gewöhnliche (und völlig falsche) Verständnis davon zu haben, was Model in MVC ist. Modell ist keine Klasse . Wenn Sie die Klasse FooBarModel oder etwas haben, das AbstractModel erbt, tun Sie es falsch.

In der richtigen MVC ist das Model eine Schicht, die viele Klassen enthält. Ein großer Teil der Klassen kann in zwei Gruppen unterteilt werden, basierend auf der Verantwortung:

- Domain Geschäftslogik

( Lesen Sie mehr : here und here ):

Instanzen aus dieser Gruppe von Klassen befassen sich mit der Berechnung von Werten, prüfen auf unterschiedliche Bedingungen, implementieren Verkaufsregeln und erledigen den Rest, was Sie "Geschäftslogik" nennen würden. Sie haben keine Ahnung, wie Daten gespeichert werden, wo sie gespeichert sind oder sogar, wenn der Speicher an erster Stelle steht.

Domain Business-Objekt ist nicht von der Datenbank abhängig. Wenn Sie eine Rechnung erstellen, spielt es keine Rolle, woher die Daten stammen. Es kann entweder von SQL oder von einer Remote-REST-API oder sogar von einem Screenshot eines MSWord-Dokuments stammen. Die Geschäftslogik ändert sich nicht.

- Datenzugriff und Speicherung

Instanzen aus dieser Gruppe von Klassen werden manchmal als Datenzugriffsobjekte bezeichnet. Normalerweise Strukturen, die Data Mapper- Muster implementieren (nicht mit ORMs gleichen Namens verwechseln .. keine Beziehung). Hier wären Ihre SQL-Anweisungen (oder vielleicht Ihr DomDocument, weil Sie es in XML speichern).

Neben den beiden Hauptteilen gibt es eine weitere Gruppe von Instanzen / Klassen, die erwähnt werden sollten:

- Dienstleistungen

Hier kommen Ihre und Drittanbieter-Komponenten zum Einsatz. Zum Beispiel können Sie sich "Authentifizierung" als Dienst vorstellen, der durch Ihren eigenen oder externen Code bereitgestellt werden kann. Auch "Mail-Absender" wäre ein Dienst, der ein Domain-Objekt mit einem PHPMailer oder SwiftMailer oder einer eigenen Mail-Sender-Komponente zusammenfasst.

Eine weitere Quelle von services die Abstraktion auf Domänen- und Datenzugriffsschichten. Sie werden erstellt, um den von den Controllern verwendeten Code zu vereinfachen. Beispiel: Wenn Sie ein neues Benutzerkonto erstellen, müssen Sie möglicherweise mit mehreren Domänenobjekten und Mappern arbeiten . Wenn Sie jedoch einen Dienst verwenden, werden nur ein oder zwei Leitungen im Controller benötigt.

Woran man sich beim Service erinnern muss, ist, dass die gesamte Schicht dünn sein soll . Es gibt keine Geschäftslogik in Diensten. Sie sind nur dazu da, Domain-Objekte, Komponenten und Mapper zu jonglieren.

Allen gemeinsam ist, dass Dienste die View-Ebene nicht direkt beeinflussen und in einem solchen Ausmaß autonom sind, dass sie außerhalb der MVC-Struktur selbst verwendet werden können (und oft auch nicht). Auch solche selbsttragenden Strukturen erleichtern die Migration zu einem anderen Framework / Architektur aufgrund der extrem geringen Kopplung zwischen Service und der restlichen Anwendung.

Erste Frage

Könnten Sie mir bitte erklären, wie die einfachste ACL in MVC implementiert werden könnte?

Hier ist der erste Ansatz zur Verwendung von Acl in Controller ...

<?php
class MyController extends Controller {

  public function myMethod() {        
    //It is just abstract code
    $acl = new Acl();
    $acl->setController('MyController');
    $acl->setMethod('myMethod');
    $acl->getRole();
    if (!$acl->allowed()) die("You're not allowed to do it!");
    ...    
  }

}
?>

Es ist ein sehr schlechter Ansatz, und es ist Minus, dass wir Acl-Code in die Methoden jedes Controllers einfügen müssen, aber wir brauchen keine zusätzlichen Abhängigkeiten!

Der nächste Ansatz besteht darin, die Methoden aller Controller private zu machen und ACL-Code in die __call Methode des Controllers __call .

<?php
class MyController extends Controller {

  private function myMethod() {
    ...
  }

  public function __call($name, $params) {
    //It is just abstract code
    $acl = new Acl();
    $acl->setController(__CLASS__);
    $acl->setMethod($name);
    $acl->getRole();
    if (!$acl->allowed()) die("You're not allowed to do it!");
    ...   
  }

}
?>

Es ist besser als der vorherige Code, aber Hauptmissbräuche sind ...

  • Alle Methoden des Controllers sollten privat sein
  • Wir müssen ACL-Code in die __call-Methode jedes Controllers einfügen.

Der nächste Ansatz besteht darin, den Acl-Code in den übergeordneten Controller zu schreiben, aber wir müssen weiterhin alle Methoden des Kind-Controllers privat halten.

Was ist die Lösung? Und was ist die beste Praxis? Wo soll ich Acl-Funktionen aufrufen, um zu entscheiden, ob die Ausführung der Methode erlaubt oder nicht erlaubt ist.

Zweite Frage

Die zweite Frage betrifft die Rolle von Acl. Stellen wir uns vor, dass wir Gäste, Nutzer und Freunde des Nutzers haben. Der Benutzer hat nur eingeschränkten Zugriff auf sein Profil, das nur für Freunde sichtbar ist. Alle Gäste können das Profil dieses Nutzers nicht anzeigen. Also, hier ist die Logik ..

  • wir müssen sicherstellen, dass die aufgerufene Methode ein Profil ist
  • Wir müssen den Besitzer dieses Profils erkennen
  • Wir müssen erkennen, ob der Betrachter der Besitzer dieses Profils ist oder nicht
  • Wir müssen die Einschränkungsregeln für dieses Profil lesen
  • wir müssen entscheiden, die Profilmethode auszuführen oder nicht auszuführen

Die Hauptfrage besteht darin, den Besitzer des Profils zu erkennen. Wir können erkennen, wer der Besitzer des Profils ist, der nur die Methode des Modells $ model-> getOwner () ausführt, aber Acl hat keinen Zugriff auf das Modell. Wie können wir das umsetzen?

Ich hoffe, dass meine Gedanken klar sind. Entschuldigung für mein Englisch.

Vielen Dank.


Eine Möglichkeit besteht darin, alle Controller in eine andere Klasse zu integrieren, die den Controller erweitert und alle Funktionsaufrufe an die umbrochene Instanz delegieren lässt, nachdem die Berechtigung geprüft wurde.

Sie könnten es auch mehr im Upstream-Bereich im Dispatcher machen (wenn Ihre Anwendung tatsächlich einen solchen hat) und die Berechtigungen basierend auf den URLs anstelle von Kontrollmethoden suchen.

edit : Ob Sie auf eine Datenbank, einen LDAP-Server usw. zugreifen müssen, ist zu der Frage orthogonal. Mein Punkt war, dass Sie eine Autorisierung basierend auf URLs anstelle von Controller-Methoden implementieren könnten. Diese sind robuster, da Sie normalerweise Ihre URLs nicht ändern (URLs als öffentliche Schnittstelle), aber Sie können auch die Implementierungen Ihrer Controller ändern.

In der Regel verfügen Sie über eine oder mehrere Konfigurationsdateien, in denen Sie bestimmte URL-Muster bestimmten Authentifizierungsmethoden und Autorisierungsanweisungen zuordnen. Bevor der Dispatcher die Anfrage an die Controller absetzt, stellt er fest, ob der Benutzer autorisiert ist, und bricht den Versand ab, wenn er dies nicht tut.


ACL und Controller

Zuallererst: Das sind verschiedene Dinge / Schichten am häufigsten. Wenn Sie den vorbildlichen Controller-Code kritisieren, setzt er beide zusammen - am offensichtlichsten zu eng.

tereško skizzierte bereits einen Weg, wie man dies mehr mit dem Dekoratormuster entkoppeln könnte.

Ich würde zuerst einen Schritt zurückgehen, um nach dem ursprünglichen Problem zu suchen, mit dem du konfrontiert wirst, und dann ein bisschen darüber sprechen.

Auf der einen Seite wollen Sie Controller haben, die genau die Arbeit verrichten, zu der sie angewiesen sind (Befehl oder Aktion, nennen wir es Befehl).

Auf der anderen Seite möchten Sie ACL in Ihre Anwendung einbinden können. Das Arbeitsgebiet dieser ACLs sollte - wenn ich Ihre Frage richtig verstanden habe - der Zugriff auf bestimmte Befehle Ihrer Anwendungen sein.

Diese Art der Zugriffskontrolle benötigt daher etwas anderes, das diese beiden zusammenbringt. Basierend auf dem Kontext, in dem ein Befehl ausgeführt wird, tritt ACL ein und Entscheidungen müssen getroffen werden, unabhängig davon, ob ein bestimmter Befehl von einem bestimmten Subjekt (z. B. dem Benutzer) ausgeführt werden kann oder nicht.

Fassen wir zusammen, was wir haben:

  • Befehl
  • ACL
  • Benutzer

Die ACL-Komponente ist hier zentral: Sie muss zumindest etwas über den Befehl wissen (um den Befehl genau zu identifizieren) und muss den Benutzer identifizieren können. Benutzer werden normalerweise leicht durch eine eindeutige ID identifiziert. Aber oft in Webapplikationen gibt es Benutzer, die überhaupt nicht identifiziert werden, oft Gast, Anonym, Jedermann usw. In diesem Beispiel nehmen wir an, dass die ACL ein Benutzerobjekt konsumieren und diese Details einkapseln kann. Das Benutzerobjekt ist an das Anwendungsanforderungsobjekt gebunden, und die ACL kann es verwenden.

Was ist mit der Identifizierung eines Befehls? Ihre Interpretation des MVC-Musters legt nahe, dass ein Befehl eine Kombination aus einem Klassennamen und einem Methodennamen ist. Wenn wir genauer hinsehen, gibt es sogar Argumente (Parameter) für einen Befehl. Also ist es richtig zu fragen, was genau einen Befehl identifiziert? Der Klassenname, der Methodenname, die Anzahl oder Namen von Argumenten, sogar die Daten in einem der Argumente oder eine Mischung aus all dem?

Je nachdem, in welcher Detailebene Sie einen Befehl in Ihrer ACL angeben müssen, kann dies sehr unterschiedlich sein. Für das Beispiel lassen Sie es einfach und geben an, dass ein Befehl durch den Klassennamen und den Methodennamen identifiziert wird.

Der Kontext, wie diese drei Teile (ACL, Command und User) zueinander gehören, ist nun klarer.

Wir könnten sagen, mit einer imaginären ACL-Komponente können wir bereits Folgendes tun:

$acl->commandAllowedForUser($command, $user);

Sehen Sie sich einfach an, was hier passiert: Indem Sie sowohl den Befehl als auch den Benutzer identifizierbar machen, kann die ACL ihre Arbeit tun. Der Job der ACL steht nicht im Zusammenhang mit der Arbeit des Benutzerobjekts und des konkreten Befehls.

Es fehlt nur ein Teil, dieser kann nicht in der Luft leben. Und das tut es nicht. Sie müssen also den Ort ausfindig machen, an dem die Zugriffskontrolle eingreifen muss. Schauen wir uns an, was in einer Standard-Webapplikation passiert:

User -> Browser -> Request (HTTP)
   -> Request (Command) -> Action (Command) -> Response (Command) 
   -> Response(HTTP) -> Browser -> User

Um diesen Ort zu finden, müssen wir wissen, dass der konkrete Befehl ausgeführt werden muss, damit wir diese Liste reduzieren können und nur die folgenden (potenziellen) Orte betrachten müssen:

User -> Browser -> Request (HTTP)
   -> Request (Command)

Irgendwann in Ihrer Anwendung wissen Sie, dass ein bestimmter Benutzer aufgefordert hat, einen konkreten Befehl auszuführen. Sie führen hier bereits eine ACL-Operation durch: Wenn ein Benutzer einen Befehl anfordert, der nicht existiert, erlauben Sie nicht, dass dieser Befehl ausgeführt wird. Was auch immer in Ihrer Anwendung passiert, ist möglicherweise ein guter Ort, um die "echten" ACL-Prüfungen hinzuzufügen:

Der Befehl wurde gefunden und wir können ihn identifizieren, damit die ACL damit umgehen kann. Falls der Befehl für einen Benutzer nicht erlaubt ist, wird der Befehl nicht ausgeführt (Aktion). Vielleicht eine CommandNotAllowedResponse anstelle der CommandNotFoundResponse für den Fall, dass eine Anfrage nicht in einen konkreten Befehl aufgelöst werden konnte.

Der Ort, an dem das Mapping eines konkreten HTTPRequest auf einen Befehl abgebildet wird, wird oft als Routing bezeichnet . Da das Routing bereits die Aufgabe hat, einen Befehl zu lokalisieren, warum sollte es nicht erweitert werden, um zu prüfen, ob der Befehl tatsächlich pro ACL erlaubt ist? ZB indem der Router auf einen ACL-fähigen Router erweitert wird: RouterACL . Wenn Ihr Router den User noch nicht kennt, ist der Router nicht der richtige Ort, da für die ACL nicht nur der Befehl, sondern auch der Benutzer identifiziert werden muss. Dieser Ort kann also variieren, aber ich bin mir sicher, dass Sie den Ort, den Sie erweitern müssen, leicht finden können, da er die Anforderungen des Benutzers und des Befehls erfüllt:

User -> Browser -> Request (HTTP)
   -> Request (Command)

Der Benutzer ist seit dem Anfang verfügbar, Befehl zuerst mit Request(Command) .

Anstatt also die ACL-Prüfungen in die konkrete Implementierung eines jeden Befehls einzufügen, platzieren Sie sie davor. Du brauchst keine schweren Muster, Magie oder was auch immer, die ACL macht ihren Job, der Benutzer erledigt seine Aufgabe und vor allem der Befehl, es ist seine Aufgabe: Nur der Befehl, sonst nichts. Der Befehl hat kein Interesse zu wissen, ob Rollen auf ihn zutreffen, ob er irgendwo geschützt ist oder nicht.

Also halt Dinge auseinander, die nicht zueinander gehören. Verwenden Sie eine leichte Umformulierung des Single Responsibility Principle (SRP) : Es sollte nur einen Grund geben, einen Befehl zu ändern - weil sich der Befehl geändert hat. Nicht, weil Sie jetzt ACL in Ihre Anwendung einführen. Nicht, weil Sie das Benutzerobjekt wechseln. Nicht, weil Sie von einer HTTP / HTML-Schnittstelle zu einer SOAP- oder Befehlszeilenschnittstelle migrieren.

Die ACL steuert in Ihrem Fall den Zugriff auf einen Befehl, nicht den Befehl selbst.







acl