Wie kann ich eine Zugriffssteuerungsliste in meiner Web-MVC-Anwendung implementieren?


96

Erste Frage

Können Sie mir bitte erklären, wie die einfachste ACL in MVC implementiert werden kann?

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 das Minus ist, dass wir Acl-Code in die Methode jedes Controllers einfügen müssen, aber wir brauchen keine zusätzlichen Abhängigkeiten!

Der nächste Ansatz besteht darin, alle Methoden des Controllers zu privateerstellen und ACL-Code zur __callMethode des Controllers hinzuzufügen .

<?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 die Hauptminuspunkte 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, Acl-Code in den übergeordneten Controller einzufügen, aber wir müssen weiterhin alle Methoden des untergeordneten Controllers privat halten.

Was ist die Lösung? Und was ist die beste Vorgehensweise? Wo soll ich Acl-Funktionen aufrufen, um zu entscheiden, ob eine Methode ausgeführt oder nicht zugelassen werden soll?

Zweite Frage

Bei der zweiten Frage geht es darum, mit Acl eine Rolle zu bekommen. Stellen wir uns vor, wir haben Gäste, Benutzer und Freunde des Benutzers. Der Benutzer hat nur eingeschränkten Zugriff auf die Anzeige seines Profils, die nur von Freunden angezeigt werden kann. Alle Gäste können das Profil dieses Benutzers nicht anzeigen. Also, hier ist die Logik ..

  • Wir müssen sicherstellen, dass die aufgerufene Methode Profil ist
  • Wir müssen den Besitzer dieses Profils ermitteln
  • Wir müssen feststellen, ob der Betrachter Eigentümer dieses Profils ist oder nicht
  • Wir müssen die Einschränkungsregeln für dieses Profil lesen
  • Wir müssen entscheiden, ob die Profilmethode ausgeführt werden soll oder nicht

Die Hauptfrage betrifft die Erkennung des Profilbesitzers. Wir können erkennen, wer Eigentümer des Profils ist, indem wir nur die Modellmethode $ model-> getOwner () ausführen, 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.

Danke dir.


1
Ich verstehe nicht einmal, warum Sie "Zugriffssteuerungslisten" für Benutzerinteraktionen benötigen würden. Würden Sie nicht einfach etwas sagen wie if($user->hasFriend($other_user) || $other_user->profileIsPublic()) $other_user->renderProfile()(sonst "Sie haben keinen Zugriff auf das Profil dieses Benutzers" oder so etwas? Ich verstehe es nicht.
Buttle Butkus

2
Wahrscheinlich, weil Kirzilla alle Bedingungen für den Zugriff an einem Ort verwalten möchte - hauptsächlich in der Konfiguration. Änderungen an Berechtigungen können also in Admin vorgenommen werden, anstatt den Code zu ändern.
Mariyo

Antworten:


185

Erster Teil / Antwort (ACL-Implementierung)

Meiner bescheidenen Meinung nach wäre der beste Weg, dies zu erreichen, die Verwendung eines Dekorationsmusters . Grundsätzlich bedeutet dies, dass Sie Ihr Objekt nehmen und es in ein anderes Objekt legen , das wie eine Schutzhü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ürden Sie 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 Überprüfung auf Autorisierung erfolgt außerhalb des Zielobjekts. Dies bedeutet, dass:
    • Das ursprüngliche Objekt ist nicht für die Zugriffskontrolle verantwortlich und hält sich an SRP
    • Wenn Sie "Berechtigung verweigert" erhalten, sind Sie nicht in einem Controller gesperrt, mehr Optionen
  3. Sie können diese gesicherte Instanz in jedes andere Objekt einfügen, der Schutz bleibt erhalten
  4. wickeln Sie es ein und vergessen Sie es. Sie können so tun , als wäre es das Originalobjekt. Es wird genauso reagieren

Es gibt jedoch auch ein Hauptproblem bei dieser Methode: Sie können nicht nativ überprüfen, ob gesicherte Objektimplemente und Schnittstellen (die auch zum Nachschlagen vorhandener Methoden gelten) oder Teil einer Vererbungskette sind.

Zweiter Teil / Antwort (RBAC für Objekte)

In diesem Fall besteht der Hauptunterschied darin, dass Ihre Domänenobjekte (im Beispiel :) Profileselbst Details zum Eigentümer enthalten. Dies bedeutet, dass Sie, um zu überprüfen, ob (und auf welcher Ebene) der Benutzer Zugriff darauf hat, diese Zeile ändern müssen:

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

Im Wesentlichen haben Sie zwei Möglichkeiten:

  • Stellen Sie der ACL das betreffende Objekt zur Verfügung. Aber Sie müssen aufpassen, dass Sie nicht gegen das Gesetz von Demeter verstoßen :

    $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. Dadurch wird sie auch ein bisschen benutzerfreundlicher:

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

Paar Videos, die Ihnen helfen könnten, Ihre eigene Implementierung zu entwickeln:

Randnotizen

Sie scheinen das weit verbreitete (und völlig falsche) Verständnis dafür zu haben, was Modell in MVC ist. Modell ist keine Klasse . Wenn Sie eine Klasse mit dem Namen FooBarModeloder etwas, das erbt AbstractModel, haben, machen Sie es falsch.

In der richtigen MVC ist das Modell eine Ebene, die viele Klassen enthält. Ein großer Teil der Klassen kann je nach Verantwortung in zwei Gruppen unterteilt werden:

- Domain Business Logic

( lesen Sie mehr : hier und hier ):

Instanzen aus dieser Klasse von Klassen befassen sich mit der Berechnung von Werten, prüfen auf unterschiedliche Bedingungen, implementieren Verkaufsregeln und erledigen den Rest, was Sie als "Geschäftslogik" bezeichnen würden. Sie haben keine Ahnung, wie Daten gespeichert werden, wo sie gespeichert werden oder ob Speicher überhaupt vorhanden sind.

Das Domain Business-Objekt hängt nicht von der Datenbank ab. Wenn Sie eine Rechnung erstellen, spielt es keine Rolle, woher die Daten stammen. Es kann entweder aus SQL oder einer Remote-REST-API oder sogar aus 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 ein Data Mapper- Muster implementieren (nicht mit gleichnamigen ORMs verwechseln. Keine Beziehung). Hier befinden sich Ihre SQL-Anweisungen (oder möglicherweise Ihr DomDocument, weil Sie es in XML speichern).

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

- Dienstleistungen

Hier kommen Ihre und Komponenten von Drittanbietern ins Spiel. Sie können sich beispielsweise "Authentifizierung" als Dienst vorstellen, der von Ihnen selbst bereitgestellt werden kann, oder als externen Code. "Mail-Absender" wäre auch ein Dienst, der ein Domänenobjekt mit einem PHPMailer oder SwiftMailer oder Ihrer eigenen Mail-Absender-Komponente zusammenfügen könnte.

Eine weitere Quelle für Dienste ist die Abstraktion auf Domänen- und Datenzugriffsebenen. Sie werden erstellt, um den von Controllern verwendeten Code zu vereinfachen. Zum Beispiel: neue Benutzerkonten könnten zur Arbeit mit mehreren erfordert Domänenobjekte und Mapper . Bei Verwendung eines Dienstes werden jedoch nur ein oder zwei Leitungen in der Steuerung benötigt.

Was Sie bei der Erbringung von Dienstleistungen beachten müssen, ist, dass die gesamte Schicht dünn sein soll . Es gibt keine Geschäftslogik in Diensten. Sie dienen nur zum Jonglieren von Domänenobjekten, Komponenten und Zuordnungen.

Allen gemeinsam ist, dass Dienste die Ansichtsebene nicht direkt beeinflussen und so autonom sind, dass sie außerhalb der MVC-Struktur selbst verwendet werden können (und häufig beendet werden). Solche autarken Strukturen erleichtern auch die Migration auf ein anderes Framework / eine andere Architektur erheblich, da die Kopplung zwischen Service und dem Rest der Anwendung äußerst gering ist.


34
Ich habe gerade in 5 Minuten mehr gelernt, als in Monaten. Würden Sie zustimmen: Thin Controller werden an Dienste gesendet, die Ansichtsdaten sammeln? Wenn Sie jemals Fragen direkt annehmen, senden Sie mir bitte eine Nachricht.
Stephane

2
Ich stimme teilweise zu. Die Erfassung von Daten aus der Ansicht erfolgt außerhalb der MVC-Triade, wenn Sie die RequestInstanz (oder ein Analogon davon) initialisieren . Der Controller extrahiert nur Daten aus der RequestInstanz und übergibt den größten Teil an die richtigen Dienste (ein Teil davon wird auch angezeigt). Dienste führen Vorgänge aus, zu denen Sie ihnen befohlen haben. Wenn view dann die Antwort generiert, fordert es Daten von Diensten an und generiert basierend auf diesen Informationen die Antwort. Diese Antwort kann entweder HTML sein, das aus mehreren Vorlagen erstellt wurde, oder nur ein HTTP-Speicherortheader. Hängt vom vom Controller eingestellten Status ab.
Tereško

4
Um eine vereinfachte Erklärung zu verwenden: Controller "schreibt" in Modell und Ansicht, Ansicht "liest" aus Modell. Die Modellebene ist die passive Struktur in allen webbezogenen Mustern, die von MVC inspiriert wurden.
Tereško

@Stephane, wenn Sie Fragen direkt stellen möchten, können Sie mir jederzeit eine Nachricht auf Twitter senden. Oder warst du eine Art "Langform", die nicht mit 140 Zeichen vollgestopft werden kann?
Tereško

Liest aus dem Modell: Bedeutet das eine aktive Rolle für das Modell? Das habe ich noch nie gehört. Ich kann Ihnen jederzeit einen Link über Twitter senden, wenn Sie dies bevorzugen. Wie Sie sehen können, werden diese Antworten schnell zu Gesprächen, und ich habe versucht, diese Website und Ihre Twitter-Follower zu respektieren.
Stephane

16

ACL und Controller

Zuallererst: Dies sind meistens verschiedene Dinge / Schichten. Wenn Sie den beispielhaften Controller-Code kritisieren, werden beide zusammengefügt - am offensichtlichsten zu eng.

tereško hat bereits einen Weg aufgezeigt , wie Sie dies mit dem Dekorationsmuster mehr entkoppeln können.

Ich würde zuerst einen Schritt zurückgehen, um nach dem ursprünglichen Problem zu suchen, mit dem Sie konfrontiert sind, und das dann ein wenig diskutieren.

Einerseits möchten Sie Controller haben, die nur die Arbeit erledigen, die ihnen befohlen wurde (Befehl oder Aktion, nennen wir es Befehl).

Auf der anderen Seite möchten Sie ACL in Ihre Anwendung einfügen können. Das Arbeitsfeld dieser ACLs sollte - wenn ich Ihre Frage richtig verstanden habe - darin bestehen, den Zugriff auf bestimmte Befehle Ihrer Anwendungen zu steuern.

Diese Art der Zugangskontrolle benötigt daher etwas anderes, das diese beiden zusammenbringt. Basierend auf dem Kontext, in dem ein Befehl ausgeführt wird, wird ACL aktiviert und es muss entschieden werden, ob ein bestimmter Befehl von einem bestimmten Betreff (z. B. dem Benutzer) ausgeführt werden kann oder nicht.

Fassen wir bis zu diesem Punkt zusammen, was wir haben:

  • Befehl
  • ACL
  • Benutzer

Die ACL-Komponente spielt hier eine zentrale Rolle: Sie muss mindestens etwas über den Befehl wissen (um den Befehl genau zu identifizieren) und den Benutzer identifizieren können. Benutzer sind normalerweise leicht durch eine eindeutige ID zu identifizieren. In Webanwendungen gibt es jedoch häufig Benutzer, die überhaupt nicht identifiziert werden, häufig als Gast, anonym, alle usw. bezeichnet. In diesem Beispiel wird davon ausgegangen, dass die ACL ein Benutzerobjekt verwenden und diese Details kapseln kann. Das Benutzerobjekt ist an das Anwendungsanforderungsobjekt gebunden und kann von der ACL verwendet werden.

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

Je nachdem, welchen Detaillierungsgrad Sie benötigen, um einen Befehl in Ihrer ACL zu identifizieren, kann dies sehr unterschiedlich sein. Lassen Sie es uns für das Beispiel einfach halten und angeben, dass ein Befehl durch den Klassennamen und den Methodennamen identifiziert wird.

Der Kontext, in dem diese drei Teile (ACL, Befehl und Benutzer) zueinander gehören, ist jetzt klarer.

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

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

Sehen Sie einfach, was hier passiert: Indem Sie sowohl den Befehl als auch den Benutzer identifizierbar machen, kann die ACL ihre Arbeit erledigen. Der Job der ACL hängt nicht mit der Arbeit des Benutzerobjekts und des konkreten Befehls zusammen.

Es fehlt nur ein Teil, dieser kann nicht in der Luft leben. Und das tut es nicht. Sie müssen also den Ort finden, an dem die Zugriffskontrolle aktiviert werden muss. Schauen wir uns an, was in einer Standard-Webanwendung 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 er vor der Ausführung des konkreten Befehls ausgeführt werden muss, damit wir diese Liste reduzieren können und nur die folgenden (potenziellen) Orte untersuchen müssen:

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

Irgendwann in Ihrer Anwendung wissen Sie, dass ein bestimmter Benutzer die Ausführung eines konkreten Befehls angefordert hat. Sie führen hier bereits eine Art ACL durch: Wenn ein Benutzer einen Befehl anfordert, der nicht vorhanden ist, lassen Sie diesen Befehl nicht ausführen. Wo immer dies in Ihrer Anwendung geschieht, ist dies 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 zulässig ist, wird der Befehl nicht ausgeführt (Aktion). Möglicherweise konnte eine CommandNotAllowedResponseanstelle der CommandNotFoundResponsefür den Fall geltenden Anfrage nicht auf einen konkreten Befehl aufgelöst werden.

Der Ort, an dem die Zuordnung einer konkreten HTTPRequest einem Befehl zugeordnet wird, wird häufig als Routing bezeichnet . Da das Routing bereits die Aufgabe hat, einen Befehl zu finden, können Sie ihn erweitern, um zu überprüfen, ob der Befehl tatsächlich pro ACL zulässig ist. ZB durch Erweitern des Router auf einen ACL-fähigen Router : RouterACL. Wenn Ihr Router das noch nicht kennt User, Routerist das nicht der richtige Ort, denn damit die ACL funktioniert, muss nicht nur der Befehl, sondern auch der Benutzer identifiziert werden. Dieser Ort kann also variieren, aber ich bin sicher, dass Sie den Ort, den Sie erweitern müssen, leicht finden können, da dieser Ort die Benutzer- und Befehlsanforderungen erfüllt:

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

Benutzer ist von Anfang an verfügbar, Befehl zuerst mit Request(Command).

Anstatt Ihre ACL-Prüfungen in die konkrete Implementierung jedes Befehls einzufügen, platzieren Sie sie davor. Sie brauchen keine schweren Muster, Magie oder was auch immer, die ACL erledigt ihren Job, der Benutzer erledigt ihren Job und insbesondere der Befehl erledigt seinen Job: Nur der Befehl, sonst nichts. Der Befehl hat kein Interesse daran zu wissen, ob Rollen für ihn gelten oder nicht, ob er irgendwo bewacht ist oder nicht.

Halten Sie also einfach 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 - da sich der Befehl geändert hat. Nicht, weil Sie jetzt ACL'ing 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 in Ihrem Fall steuert den Zugriff auf einen Befehl, nicht den Befehl selbst.


Zwei Fragen: CommandNotFoundResponse & CommandNotAllowedResponse: Würden Sie diese von der ACL-Klasse an den Router oder Controller übergeben und eine universelle Antwort erwarten? 2: Wenn Sie Methode + Attribute einschließen möchten, wie würden Sie damit umgehen?
Stephane

1: Antwort ist Antwort, hier nicht von ACL, sondern vom Router. ACL hilft dem Router, den Antworttyp herauszufinden (nicht gefunden, insbesondere: verboten). 2: Kommt darauf an. Wenn Sie Attribute als Parameter aus Aktionen meinen und eine ACL mit Parametern benötigen, fügen Sie diese unter ACL ein.
hakre

13

Eine Möglichkeit besteht darin, alle Ihre Controller in eine andere Klasse einzuschließen, die Controller erweitert, und alle Funktionsaufrufe nach Überprüfung der Autorisierung an die umschlossene Instanz zu delegieren.

Sie können dies auch vorgelagerter im Dispatcher tun (sofern Ihre Anwendung tatsächlich eine hat) und die Berechtigungen anhand der URLs anstelle der Steuermethoden nachschlagen.

Bearbeiten : Ob Sie auf eine Datenbank, einen LDAP-Server usw. zugreifen müssen, ist orthogonal zur Frage. Mein Punkt war, dass Sie eine Autorisierung basierend auf URLs anstelle von Controller-Methoden implementieren konnten. Diese sind robuster, da Sie Ihre URLs normalerweise nicht ändern (URLs-Bereich 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. Der Dispatcher bestimmt vor dem Versenden der Anforderung an die Controller, ob der Benutzer autorisiert ist, und bricht den Versand ab, wenn er dies nicht ist.


Könnten Sie bitte Ihre Antwort aktualisieren und weitere Details zu Dispatcher hinzufügen? Ich habe einen Dispatcher - er erkennt, welche Controller-Methode ich per URL aufrufen soll. Aber ich kann nicht verstehen, wie ich eine Rolle in Dispatcher bekommen kann (ich muss auf die Datenbank zugreifen, um dies zu tun). Ich hoffe wir sehen uns bald.
Kirzilla

Aha, hast du deine Idee? Ich sollte entscheiden, ob die Ausführung ohne Zugriff auf die Methode zulässig ist oder nicht! Daumen hoch! Die letzte ungelöste Frage - wie man von Acl aus auf das Modell zugreift. Irgendwelche Ideen?
Kirzilla

@ Kirzilla Ich habe die gleichen Probleme mit Controllern. Es scheint, als müssten irgendwo Abhängigkeiten vorhanden sein. Auch wenn die ACL nicht vorhanden ist, was ist mit der Modellebene? Wie können Sie verhindern, dass dies eine Abhängigkeit ist?
Stephane
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.