Как я могу реализовать список управления доступом в своем веб-приложении 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. Давайте представим, что у нас есть гости, пользователи и друзья пользователя. Пользователь ограничил доступ к просмотру своего профиля, что только друзья могут просматривать его. Все гости не могут просматривать профиль этого пользователя. Итак, вот такая логика..

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

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

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

спасибо.

3 91

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. оберните его и забыть его .. вы можете претендует что это оригинальный объект, он будет реагировать так же

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

вторая часть / ответ (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, либо из удаленного REST API, либо даже скриншот документа MSWord. Бизнес-логика не меняется.

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

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

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

-услуги

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

в зависимости от того, какой уровень детализации вам нужно определить команду в вашем 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 в своем приложении. Не потому, что вы переключаете объект пользователя. Не потому, что вы переходите от интерфейса HTTP/HTML к интерфейсу SOAP или командной строки.

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

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

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

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

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