Jak zaimplementować listę kontroli dostępu w aplikacji Web MVC?

Pierwsze pytanie

Proszę, czy mógłbyś mi wyjaśnić, w jaki sposób można zaimplementować ACL w MVC.

Oto pierwsze podejście do użycia Acl w kontrolerze...

<?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!");
    ...    
  }

}
?>

Jest to bardzo złe podejście, a minusem jest to, że musimy dodać fragment kodu Acl do metody każdego kontrolera, ale nie potrzebujemy żadnych dodatkowych zależności!

Następnym podejściem jest wykonanie wszystkich metod kontrolera private i dodanie kodu ACL do kontrolera __call metoda.

<?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!");
    ...   
  }

}
?>

Jest lepszy od poprzedniego kodu, ale główne minusy są...

  • wszystkie metody kontrolera powinny być prywatne
  • musimy dodać kod ACL do metody __call każdego kontrolera.

Następnym podejściem jest umieszczenie kodu Acl w kontrolerze nadrzędnym, ale nadal musimy zachować prywatność wszystkich metod kontrolera podrzędnego.

Jakie jest rozwiązanie? A jaka jest najlepsza praktyka? Gdzie należy wywołać funkcje Acl, aby zdecydować o metodzie allow lub disallow do egzekucji.

Drugie pytanie

Drugie pytanie dotyczy uzyskania roli za pomocą Acl. Wyobraźmy sobie, że mamy Gości, użytkowników i znajomych użytkownika. Użytkownik ma ograniczony dostęp do przeglądania swojego profilu, że tylko znajomi mogą go przeglądać. Wszyscy goście nie mogą zobaczyć profilu tego użytkownika. Oto logika..

  • musimy upewnić się, że wywołana metoda to profil
  • W tym celu musimy wykryć właściciela tego profilu.]}
  • musimy wykryć is viewer is owner tego profilu lub nr
  • musimy przeczytać zasady dotyczące tego profilu
  • musimy zdecydować o execute or not execute profile method

Głównym pytaniem jest wykrywanie właściciela profilu. Możemy wykryć, kto jest właścicielem profilu tylko wykonując metodę modelu $model- > getOwner (), ale Acl nie mają dostępu do modelu. Jak możemy to wdrożyć?

Mam nadzieję, że moje myśli są jasne. Przepraszam za mój angielski. Dziękuję.
Author: Madara Uchiha, 2010-08-07

3 answers

Pierwsza część / odpowiedź (implementacja ACL)

Moim skromnym zdaniem, najlepszym sposobem podejścia do tego byłoby użycie wzoru dekoratora , zasadniczo oznacza to, że bierzesz swój obiekt i umieszczasz go wewnątrz innego obiektu, który będzie działał jak powłoka ochronna. Nie wymagałoby to przedłużenia oryginalnej klasy. Oto przykład:

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
            );
        }
    }

}

I tak można by używać tego rodzaju struktury:

// 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();

Jak można zauważyć, to rozwiązanie ma kilka zalet:

  1. zabezpieczenie może być użyte na dowolnym obiekcie, nie tylko instancjach Controller
  2. sprawdzanie autoryzacji odbywa się poza obiektem docelowym, co oznacza, że:
    • oryginalny obiekt nie odpowiada za kontrolę dostępu, przylega do SRP
    • gdy otrzymasz "Odmowa uprawnień", nie jesteś zablokowany wewnątrz kontrolera, więcej opcji
  3. możesz wprowadzić tę zabezpieczoną instancję w dowolnym innym obiekt, zachowa ochronę
  4. wrap it & forget it .. możesz udawać że jest to oryginalny obiekt, zareaguje tak samo

Ale , jest też jeden poważny problem z tą metodą - nie można natywnie sprawdzić, czy secured object implementuje i interfejs (co dotyczy również wyszukiwania istniejących metod) lub jest częścią jakiegoś łańcucha dziedziczenia.

Druga część / odpowiedź (RBAC dla obiektów)

W tym przypadku główna różnica powinieneś rozpoznać, że ty Obiekty domeny (W przykładzie: Profile) sama zawiera szczegóły dotyczące właściciela. Oznacza to, że aby sprawdzić, czy (i na jakim poziomie) użytkownik ma do niego dostęp, będzie wymagał zmiany tej linii: {]}

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

Zasadniczo masz dwie opcje:

  • Podaj ACL z danym obiektem. Ale trzeba uważać, aby nie naruszyć prawa Demeter :

    $this->acl->isAllowed( get_class($this->target), $method )
    
  • Prośba o wszystkie odpowiednie ACL może być używany tylko do testowania jednostek, co czyni go nieco bardziej przyjaznym dla testów jednostkowych.]}

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

Kilka filmów, które mogą pomóc ci wymyślić własną realizację:

Side notes

Wydaje się, że masz dość powszechne (i całkowicie błędne ) zrozumienie, jaki Model w MVC jest. Model nie jest Klasa . Jeśli masz klasę o nazwie FooBarModel lub coś, co dziedziczy AbstractModel, robisz to źle.

W odpowiednim MVC model jest warstwą, która zawiera wiele klas. Duża część klas może być podzielona na dwie grupy, w zależności od odpowiedzialności:

- Domain Business Logic

(Czytaj więcej: tutaj i tutaj):

Instancje z tej grupy klas zajmują się obliczaniem wartości, sprawdzają różne warunki, wdrożyć zasady sprzedaży i zrobić całą resztę, co można nazwać "logika biznesowa". Nie mają pojęcia, w jaki sposób dane są przechowywane, gdzie są przechowywane, a nawet czy pamięć istnieje na pierwszym miejscu.

Domain Business object nie zależy od bazy danych. Podczas tworzenia faktury nie ma znaczenia, skąd pochodzą dane. Może to być z SQL lub ze zdalnego API REST, a nawet zrzut ekranu dokumentu MSWord. Logika biznesowa nie zmienia się.

- dane Dostęp i przechowywanie

Instancje z tej grupy klas są czasami nazywane obiektami dostępu do danych. Zazwyczaj struktury implementujące wzorzec Data Mapper (nie mylić z ORM o tej samej nazwie .. brak pokrewieństwa). To jest miejsce, gdzie będą twoje instrukcje SQL (lub może Twój DomDocument, ponieważ przechowujesz go w XML).

Oprócz dwóch głównych części, istnieje jeszcze jedna grupa instancji/klas, o których należy wspomnieć:

- usługi

To miejsce, w którym twoje i zewnętrzne komponenty wchodzą w grę. Na przykład możesz myśleć o "uwierzytelnianiu" jako o usłudze, która może być dostarczona przez twój własny lub Zewnętrzny kod. Również "mail sender" będzie usługą, która może połączyć jakiś obiekt domeny z PHPMailer lub SwiftMailer, lub własny komponent mail-sender.

Innym źródłem usług są abstrakcje na domenie i warstwy dostępu do danych. Są one tworzone do uprość kod używany przez kontrolery. Na przykład: utworzenie nowego konta użytkownika może wymagać pracy z kilkoma obiektami domeny i maperami . Ale korzystając z usługi, będzie potrzebował tylko jednej lub dwóch linii w kontrolerze.

Przy tworzeniu usług należy pamiętać, że cała warstwa powinna być cienka. W usługach nie ma logiki biznesowej. Są tam tylko po to, aby żonglować obiektami domeny, komponentami i maperami.

Jeden z łączy je to, że usługi nie wpływają bezpośrednio na warstwę widoku i są autonomiczne w takim stopniu, że mogą być ( i często są ) używane poza samą strukturą MVC. Również takie samowystarczalne struktury znacznie ułatwiają migrację na inny framework/architekturę, ze względu na bardzo niskie sprzężenie usługi z resztą aplikacji.

 175
Author: tereško,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-10-27 17:03:18

ACL i kontrolery

Po pierwsze: Są to najczęściej różne rzeczy / warstwy. Krytykując przykładowy kod kontrolera, łączy on oba - najwyraźniej zbyt ciasno.

Tereško nakreślił już sposób, w jaki można to bardziej oddzielić od wzoru dekoratora.

Cofnąłbym się o krok do tyłu, by poszukać pierwotnego problemu, z którym się borykasz i przedyskutować to trochę później.

Z jednej strony chcesz mieć kontrolery, które po prostu wykonuj pracę, do której są nakazane (polecenie lub akcja, nazwijmy to komendą).

Z drugiej strony chcesz być w stanie umieścić ACL w swojej aplikacji. Obszarem pracy tych ACL powinno być - jeśli dobrze zrozumiałem twoje pytanie-kontrolowanie dostępu do niektórych poleceń Twoich aplikacji.

Tego rodzaju Kontrola dostępu wymaga zatem czegoś innego, co łączy te dwa elementy. Na podstawie kontekstu, w którym wykonywane jest polecenie, ACL i decyzje muszą należy to zrobić, czy konkretne polecenie może być wykonane przez określony podmiot (np. użytkownika).

Podsumujmy to, co mamy:

  • Komenda
  • ACL
  • użytkownik

Komponent ACL jest tutaj centralny: musi wiedzieć przynajmniej coś o poleceniu (aby dokładnie zidentyfikować polecenie) i musi być w stanie zidentyfikować użytkownika. Użytkownicy są zwykle łatwo identyfikowani za pomocą unikalnego identyfikatora. Ale często w webaplikacjach są użytkownicy, którzy nie są w ogóle identyfikowani, często nazywani gośćmi, anonimowymi, wszystkimi itp.. W tym przykładzie zakładamy, że ACL może pochłonąć obiekt użytkownika i zamknąć te szczegóły. Obiekt user jest powiązany z obiektem żądania aplikacji i ACL może go wykorzystać.

A co z identyfikacją komendy? Twoja interpretacja wzorca MVC sugeruje, że polecenie jest złożone z nazwy klasy i nazwy metody. Jeśli przyjrzymy się bliżej, są nawet argumenty (parametry) dla polecenia. Więc warto zapytać, co dokładnie identyfikuje polecenie? Nazwa klasy, nazwa metody, Liczba lub nazwy argumentów, nawet dane wewnątrz każdego z argumentów lub mieszanina tego wszystkiego?

W zależności od tego, jaki poziom szczegółów potrzebujesz, aby zidentyfikować polecenie w ACL ' ingu, może się to znacznie różnić. Dla przykładu zachowajmy to w prosty sposób i określ, że polecenie jest identyfikowane przez nazwę klasy i nazwę metody.

Więc kontekst jak te trzy części (ACL, Command i User) należą do siebie jest teraz bardziej jasne.

Możemy powiedzieć, że z wyimaginowanym kompontentem ACL możemy już zrobić, co następuje:

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

Po prostu zobacz, co się tutaj dzieje: poprzez identyfikację zarówno komendy, jak i użytkownika, ACL może wykonać swoją pracę. Zadanie ACL nie jest związane z pracą zarówno obiektu user, jak i konkretnego polecenia.

Brakuje tylko jednej części, która nie może żyć w powietrzu. I nie ma. aby zlokalizować miejsce, w którym Kontrola dostępu musi zacząć działać. Spójrzmy, co dzieje się w standardowej aplikacji webowej:
User -> Browser -> Request (HTTP)
   -> Request (Command) -> Action (Command) -> Response (Command) 
   -> Response(HTTP) -> Browser -> User

Aby zlokalizować to miejsce, wiemy, że musi to być przed wykonaniem konkretnego polecenia, więc możemy zmniejszyć tę listę i wystarczy spojrzeć na następujące (potencjalne) miejsca:

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

W pewnym momencie w Twojej aplikacji wiesz, że konkretny użytkownik zażądał wykonania konkretnego polecenia. Już robisz jakieś ACL ' in tutaj: jeśli użytkownik żąda polecenia, które nie istnieje, nie zezwalasz na wykonanie tego polecenia. Tak więc, gdzie-kiedykolwiek to się dzieje w Twojej aplikacji, może być dobrym miejscem do dodania" prawdziwych " kontroli ACL: {]}

Komenda została zlokalizowana i możemy utworzyć jej identyfikację, aby ACL mógł sobie z nią poradzić. W przypadku, gdy polecenie nie jest dozwolone dla użytkownika, polecenie nie zostanie wykonane(akcja). Może CommandNotAllowedResponse zamiast CommandNotFoundResponse dla sprawy wniosek nie może być rozwiązany na betonie dowództwo.

Miejsce, w którym mapowanie konkretnego HTTPRequest jest mapowane na polecenie, jest często nazywane Routing . Ponieważ Routing ma już zadanie zlokalizowania polecenia, dlaczego nie rozszerzyć go, aby sprawdzić, czy polecenie jest rzeczywiście dozwolone dla ACL? Np. poprzez rozszerzenie Router na router ACL aware: RouterACL. Jeśli twój router nie zna jeszcze User, to Router nie jest właściwym miejscem, ponieważ aby ACL ' ING działał nie tylko Komenda, ale także użytkownik musi być zidentyfikowany. Więc to miejsce może się różnić, ale jestem pewien, że możesz łatwo zlokalizować miejsce, które musisz rozszerzyć, ponieważ jest to miejsce, które spełnia wymagania użytkownika i polecenia: {]}

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

Użytkownik jest dostępny od początku, polecenie najpierw z Request(Command).

Więc zamiast umieszczać swoje czeki ACL wewnątrz każdej konkretnej implementacji, umieszczasz je przed nią. Nie potrzebujesz żadnych ciężkich wzorów, magii czy czegokolwiek, ACL robi swoje, użytkownik robi swoje i zwłaszcza polecenie wykonuje swoje zadanie: tylko polecenie, nic więcej. Polecenie nie ma żadnego interesu, aby wiedzieć, czy role się do niego odnoszą, czy jest gdzieś strzeżone, czy nie.

Więc po prostu trzymaj rzeczy z dala od siebie, które nie należą do siebie. Użyj lekkiego przeredagowania Zasady pojedynczej odpowiedzialności (SRP) : powinien być tylko jeden powód, aby zmienić polecenie - ponieważ polecenie się zmieniło. Nie dlatego, że teraz wprowadzasz ACL ' ing do swojej aplikacji. Nie dlatego, że zmieniasz obiekt użytkownika. Nie dlatego, że migrujesz z interfejsu HTTP / HTML do interfejsu SOAP lub Wiersza poleceń.

ACL w Twoim przypadku kontroluje dostęp do polecenia, a nie do samego polecenia.

 16
Author: hakre,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:26:12

Jedną z możliwości jest zawinięcie wszystkich kontrolerów w inną klasę, która rozszerza kontroler i deleguje wszystkie wywołania funkcji do zawiniętej instancji po sprawdzeniu autoryzacji.

Możesz też zrobić to bardziej na początku, w dyspozytorze (jeśli Twoja aplikacja rzeczywiście taką posiada) i wyszukać uprawnienia na podstawie adresów URL, zamiast metod kontroli.

Edit : czy potrzebujesz dostępu do bazy danych, serwera LDAP itp. jest prostopadły do pytania. Chodzi mi o to, że można zaimplementować autoryzację opartą na adresach URL zamiast metod kontrolera. Są one bardziej wytrzymałe, ponieważ zazwyczaj nie zmieniasz adresów URL( Rodzaj publicznego interfejsu Url area), ale równie dobrze możesz zmienić implementacje swoich kontrolerów.

Zazwyczaj masz jeden lub kilka plików konfiguracyjnych, w których mapujesz określone wzorce URL do określonych metod uwierzytelniania i dyrektyw autoryzacyjnych. Dyspozytora, przed wysłaniem żądania do kontrolerów, określa, czy użytkownik jest upoważniony i przerywa wysyłanie, jeśli nie jest.

 13
Author: Artefacto,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-08-07 11:33:41