Как реализовать список управления доступом в моем веб-приложении MVC?

первый вопрос

пожалуйста, не могли бы вы объяснить мне, как самый простой ACL может быть реализован в MVC.

вот первый подход использования Acl в контроллере...

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

}
?>

это очень плохой подход, и минус в том, что мы должны добавить часть кода Acl в метод каждого контроллера, но нам не нужны дополнительные зависимости!

следующий подход-сделать все методы контроллера private и добавьте код ACL в контроллер __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!");
    ...   
  }

}
?>

это лучше, чем предыдущий код, но основные минусы...

  • все методы контроллера должны быть частными
  • мы должны добавить код ACL в метод __вызова каждого контроллера.

следующий подход-поместить код Acl в родительский контроллер, но нам все равно нужно сохранить все методы дочернего контроллера частными.

какое решение? И какова наилучшая практика? Где должен ли я вызывать функции Acl, чтобы решить разрешить или запретить метод для выполнения.

второй вопрос

второй вопрос касается получения роли с помощью Acl. Давайте представим, что у нас есть гости, пользователи и друзья пользователя. Пользователь имеет ограниченный доступ к просмотру своего профиля, который могут просматривать только друзья. Все гости не могут просмотреть профиль пользователя. Итак, вот логика..

  • мы должны убедиться, что вызываемый метод профиль
  • мы должны обнаружить владельца этого профиля
  • мы должны определить, является ли viewer владельцем этого профиля или нет
  • мы должны прочитать правила ограничения об этом профиле
  • мы должны решить, выполнить или не выполнить метод профиля

основной вопрос заключается в обнаружении владельца профиля. Мы можем определить, кто является владельцем профиля, только выполняя метод модели $model - >getOwner (), но Acl не имеют доступа к модели. Как можем ли мы реализовать это?

Я надеюсь, что мои мысли понятны. Извините за мой английский.

спасибо.

3 ответов


первая часть / ответ (реализация ACL)

по моему скромному мнению, лучший способ подойти к этому-использовать шаблон "декоратор" в основном, это означает, что вы берете ваш объект, и место его внутри другой объект, который будет действовать как защитная оболочка. Это не потребует от вас расширения исходного класса. Вот пример:

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

}

и это будет, как вы используете этот вид структура:

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

как вы могли заметить, это решение имеет ряд преимуществ:

  1. сдерживание можно использовать на любом объекте, не только экземплярах Controller
  2. проверка авторизации происходит вне целевого объекта, что означает, что:
    • оригинальный объект не несет ответственности за контроль доступа, придерживается SRP
    • когда вы получаете "отказано в разрешении", вы не заблокированы внутри контроллера, больше вариантов
  3. вы можете впрыснуть этот защищенный экземпляр в любом другом объекте он сохранит защиту
  4. оберните его и забыть его .. ты можешь!--14-->претендует что это оригинальный объект, он будет реагировать так же

но, есть одна серьезная проблема с этим методом тоже-вы не можете изначально проверить, реализует ли защищенный объект и интерфейс ( который также применяется для поиска существующие методы ) или является частью некоторой цепочки наследования.

вторая часть / ответ (RBAC для объектов)

в этом случае основное различие, которое вы должны признать, заключается в том, что вы Объекты Домена (например: Profile) содержит сведения о владельце. Это означает, что для вас, чтобы проверить, имеет ли (и на каком уровне) пользователь доступ к нему, вам потребуется изменить эту строку:

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

по сути у вас есть два опции:

  • обеспечить ACL с объекта в вопрос. Но вы должны быть осторожны, чтобы не нарушать закон Деметры:

    $this->acl->isAllowed( get_class($this->target), $method )
    
  • запросите все соответствующие детали и предоставьте ACL только то, что ему нужно, что также сделает его немного более удобным для модульного тестирования:

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

пару видео, которые могут помочь вам придумать свой собственный реализация:

побочные Примечания

у вас, похоже, есть довольно распространенное ( и совершенно неправильное ) понимание того, что такое модель в MVC. модель не является классом. Если у вас есть класс с именем FooBarModel или что-то, что наследует AbstractModel затем вы делаете это неправильно.

в правильном MVC модель слой, который содержит много классов. Большая часть классов может быть разделена на две группы, исходя из ответственности:

- Доменная Бизнес-Логика

(подробнее: здесь и здесь):

экземпляры из этой группы классов занимаются вычислением значений, проверяют различные условия, реализуют правила продаж и делают все остальное, что вы бы назвали "бизнес-логикой". Они понятия не имеют, как хранятся данные, где они хранятся или даже если хранилище существует в первую очередь.

доменный бизнес-объект не зависит от базы данных. При создании счета-фактуры не имеет значения, откуда берутся данные. Это может быть либо из SQL, либо из удаленного API REST, или даже скриншот документа MSWord. Бизнес-логика не меняется.

- доступ к данным и их хранение

экземпляры, сделанные из этой группы классов иногда называются объектами доступа к данным. Обычно структуры, которые реализуют Маппер Данных pattern (не путайте с одноименными Ормами .. никак не связанный.) Здесь будут ваши операторы SQL (или, возможно, ваш DomDocument, потому что вы храните его в XML).

помимо двух основных частей, есть еще одна группа экземпляров / классов, которые следует упомянуть:

- услуги

это где ваш и 3rd партия компоненты приходят в игру. Например, вы можете думать о "аутентификации" как об услуге, которая может быть предоставлена вашим собственным или каким-то внешним кодом. Также "mail sender" будет сервисом, который может связать какой-либо объект домена с PHPMailer или SwiftMailer или вашим собственным компонентом mail-sender.

еще один источник услуги являются абстракцией на на домен и слои доступа к данным. Они создаются для упрощения кода, используемого контроллерами. Например: создание новая учетная запись пользователя может потребоваться для работы с несколькими объекты домена и мапперы. Но, используя сервис, ему понадобится только одна или две строки в контроллере.

что вы должны помнить при создании сервисов, так это то, что весь слой должен быть тонкий. В сервисах нет бизнес-логики. Они существуют только для жонглирования объектом домена, компонентами и сопоставителями.

одна из вещей, которые они все имеют в общем было бы, что сервисы не влияют на слой представления каким - либо прямым образом и являются автономными до такой степени, что они могут быть ( и часто выходят-являются ) использованы вне самой структуры MVC. Кроме того, такие автономные структуры значительно облегчают переход на другую структуру/архитектуру из-за чрезвычайно низкой связи между сервисом и остальной частью приложения.


ACL и контроллеры

прежде всего: это разные вещи / слои чаще всего. Когда вы критикуете примерный код контроллера, он объединяет оба - очевидно, слишком плотно.

tereško уже обозначил способ, как вы могли бы разделить это с шаблона "декоратор".

Я бы сначала сделал шаг назад, чтобы найти оригинальную проблему, с которой вы столкнулись, и обсудить это немного.

С одной стороны вы хотите иметь контроллеры, которые просто выполняют работу, которую им приказывают (команда или действие, назовем это командой).

С другой стороны, вы хотите иметь возможность поместить ACL в свое приложение. Область работы этих ACLs должна быть - если я правильно понял ваш вопрос - для управления доступом к определенным командам ваших приложений.

этот вид контроля доступа поэтому нуждается в чем-то еще, что объединяет эти два вместе. На основе контекста, в котором команда выполняется, ACL запускается, и решения должны быть сделаны, может ли конкретная команда быть выполнена конкретным субъектом (например, пользователем).

давайте подведем итог к этому моменту, что у нас есть:

  • команда
  • ACL
  • пользователей

компонент ACL является центральным здесь: он должен знать, по крайней мере, что-то о команде (чтобы идентифицировать команду, чтобы быть точным), и он должен быть в состоянии идентифицировать пользователя. Пользователи обычно легко идентифицируются по уникальному ID. Но часто в веб-приложениях есть пользователи, которые вообще не идентифицируются, часто называются гостевыми, анонимными, всеми и т. д.. В этом примере мы предполагаем, что ACL может использовать пользовательский объект и инкапсулировать эти сведения. Объект user привязан к объекту запроса приложения, и ACL может его использовать.

как насчет идентификации команды? Ваша интерпретация шаблона MVC предполагает, что команда является составной частью имя класса и имя метода. Если присмотреться, то для команды есть даже аргументы (параметры). Поэтому можно спросить, что именно идентифицирует команду? Имя класса, имя метода, число или имена аргументов, даже данные внутри любого из аргументов или смесь всего этого?

в зависимости от того, какой уровень детализации вам нужно определить команду в вашем ACL передоза, это могут быть очень разные. Для примера давайте просто сохраним его и определим, что команда идентифицируется с помощью имя класса и имя метода.

таким образом, контекст того, как эти три части (ACL, команда и пользователь) принадлежат друг другу, теперь более ясен.

мы могли бы сказать, с воображаемым компонентом ACL мы уже можем сделать следующее:

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

просто посмотрите, что здесь происходит: сделав и команду, и пользователя идентифицируемыми, ACL может сделать это. Работа ACL не связана с работой как пользовательского объекта, так и конкретного команда.

отсутствует только одна часть, это не может жить в воздухе. И это не так. Таким образом, вам нужно найти место, где контроль доступа должен пнуть. Давайте посмотрим, что происходит в стандартном веб-приложении:

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

чтобы найти это место, мы знаем, что это должно быть до выполнения конкретной команды, поэтому мы можем уменьшить этот список и нужно только посмотреть в следующие (потенциальные) места:

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

в какой-то момент в вашей приложение вы знаете, что конкретный пользователь запросил выполнение конкретной команды. Вы уже делаете здесь что-то вроде ACL'ING: если пользователь запрашивает команду, которая не существует, вы не позволяете этой команде выполняться. Так что где-либо, что происходит в вашем приложении, может быть хорошим местом для добавления" реальных " проверок ACL:

команда была обнаружена, и мы можем создать идентификацию ее, чтобы ACL мог справиться с ней. В случае, если команда не разрешена для пользователя, команда не будет выполнена (действие). Может быть a CommandNotAllowedResponse вместо CommandNotFoundResponse в этом случае запрос не может быть разрешен на конкретную команду.

место, где отображение конкретного HTTPRequest отображается на команду, часто называется маршрут. Как маршрут уже есть задание найти команду, почему бы не расширить его, чтобы проверить, действительно ли команда разрешена для ACL? Е. Г. расширяя Router к маршрутизатору с поддержкой ACL: RouterACL. Если ваш маршрутизатор еще не знаю User, потом Router не является правильным местом, потому что для работы ACL'ING должна быть идентифицирована не только команда, но и пользователь. Поэтому это место может отличаться, но я уверен, что вы можете легко найти место, которое вам нужно расширить, потому что это место, которое заполняет требование пользователя и команды:

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

пользователь доступен с самого начала, команда сначала с Request(Command).

поэтому вместо того, чтобы положить ACL проверяет внутри каждого конкретная реализация команды, вы помещаете ее перед ней. Вам не нужны никакие тяжелые шаблоны, магия или что-то еще, ACL делает свою работу, пользователь делает свою работу, и особенно команда делает свою работу: только команда, ничего больше. Команде не интересно знать, применяются ли к ней роли, охраняется ли она где-то или нет.

так что просто держите вещи, которые не принадлежат друг другу. Использовать немного формулировки принцип единой ответственности (SRP): должна быть только одна причина изменить команду-потому что команда изменилась. Не потому, что теперь вы вводите ACL'ING в свое приложение. Не потому, что вы переключаете объект User. Не потому, что вы переходите от интерфейса HTTP / HTML к интерфейсу SOAP или командной строки.

ACL в вашем случае управляет доступом к команде, а не самой командой.


одна из возможностей-обернуть все ваши контроллеры в другой класс, который расширяет контроллер, и делегировать все вызовы функций обернутому экземпляру после проверки авторизации.

вы также можете сделать это более вверх по течению, в диспетчере (если ваше приложение действительно имеет один) и поиск разрешений на основе URL-адресов, а не методов управления.

редактировать: требуется ли вам доступ к базе данных, серверу LDAP и т. д. есть ортогонально к вопросу. Я имел в виду, что вы можете реализовать авторизацию на основе URL-адресов вместо методов контроллера. Они более надежны, потому что обычно вы не будете изменять свои URL-адреса (вид открытого интерфейса области URL-адресов), но вы также можете изменить реализации своих контроллеров.

Как правило, у вас есть один или несколько файлов конфигурации, где вы сопоставляете определенные шаблоны URL с конкретными методами аутентификации и директивами авторизации. Диспетчер, перед отправкой запроса контроллерам определяет, авторизован ли пользователь, и прерывает отправку, если это не так.