Где должны проводиться проверки разрешений пользователей в MVC и кем?

26

Должны ли пользовательские проверки прав доступа выполняться в модели или контроллере? И кто должен обрабатывать проверки разрешений, объект User или какой-либо помощник UserManagement?

Где это должно произойти?

Проверка в контроллере:

class MyController {
  void performSomeAction() {
    if (user.hasRightPermissions()) {
      model.someAction();
    }
  }
  ...

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

Проверка в модели:

class MyModel {
  void someAction() {
    if (user.hasRightPermissions()) {
      ...
    }
  }
  ...

Помещая проверки в Модель, мы усложняем Модель, но также и не допускаем, чтобы мы случайно не позволяли пользователям делать то, что они не должны делать в Контроллере.

И кем?

Как только мы остановимся на месте, кто должен делать проверки? Пользователь?

Class User {
  bool hasPermissions(int permissionMask) {
    ...
  }
  ...

Но на самом деле не ответственность пользователя знать, что он или она может получить, так что, может быть, какой-нибудь класс помощника?

Class UserManagement {
  bool hasPermissions(User user, int permissionMask) {
    ...
  }
  ...

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

KBA
источник

Ответы:

20

Как обычно, «это зависит»

  • проверки разрешений будут работать везде, где их удобно поставить,
  • но если вы задаете технический вопрос, то ответом может быть «поместить проверки в объект, которому принадлежат данные, необходимые для выполнения проверки» (который, вероятно, является контроллером).
  • но если вы задаете философский вопрос, я предлагаю альтернативный ответ: не показывайте пользователям действия, которые им не разрешено выполнять .

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

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

Стивен А. Лоу
источник
Наше приложение реализует третий сценарий за исключением того, что мы не скрываем элементы управления, мы их отключаем. К сожалению, все это делается в программном обеспечении Winforms, так что это не очень актуально для вопроса OP.
Дейв Най
11
«расстраивает то, что я вижу кнопки для действий, которые я не могу выполнить» -> Попробуй поднять свой пост :)
Rowan Freeman
5
Недостаточно просто скрыть кнопки для действий, которые пользователь не может выполнить, сервер должен проверять каждый запрос на разрешения. Третий пункт не является «альтернативным ответом», это что-то, что нужно сделать в дополнение к проверке разрешений на стороне сервера.
Флим
@Flimm согласен, если запросы обрабатываются сервером; конкретный вопрос был о классе Controller
Стивен А. Лоу
7

Безопасность - это междисциплинарная проблема, поэтому ее необходимо реализовать на нескольких уровнях. Ниже приведен пример для MVC, но концепция применима к другим архитектурам и / или шаблонам, вам просто нужно определить точки исполнения.

Где это должно произойти?

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

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

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

Как видите, в зависимости от того, что вы хотите авторизовать, есть разные места, где это нужно сделать. Цель состоит в том, чтобы быть как можно более ненавязчивым, чтобы при изменении политики безопасности ее можно было легко применять, желательно без изменения кода вашего приложения. Это может быть недопустимо для небольших приложений, где набор разрешений довольно мал и меняется не очень часто. В корпоративных приложениях история совсем иная.

Кто должен это делать?

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

Посмотрите на XACML . Вам не нужно реализовывать его как есть, но он даст вам некоторые указания, которым вы могли бы следовать.

devnull
источник
Это лучший ответ. По некоторым причинам верхний и другие имеют дело с различиями между контроллером и представлением, или представлением и моделью, что не является тем, о чем спрашивает OP. Благодарность!
RedFur
1

Я использую следующую схему. Стоит сказать, что большинство проверок полномочий пользователей можно разделить на два общих случая:

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

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

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

interface ICheckAccess
{
    public function checkAccess(Actor $actor, $role);
}

class SomeModel implements ICheckAccess
{
    public function checkAccess(Actor $actor, $role)
    {
        // Your permissions logic can be as sophisticated as you want.
    }
}

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

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

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

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

Затем некоторый базовый абстрактный класс контроллера должен быть определен и унаследован.

abstract class ModelController
{
    // Retrieve model from database using id from action parameter.
    public abstract function loadModel($id);

    // Returns rules for user role to pass to SomeModel::checkAccess()
    // Something like array('view' => 'viewer', 'delete' => 'owner', 'update' => 'owner')
    public abstract function modelRules();

    public abstract fucntion getIdParameter();

    public function filterModelAccess()
    {
        $id = $this->getIdParameter();
        if(!$this->checkModelAccess($id))
            throw new HttpException(403);
    }

    public function checkModelAccess($id)
    {
        $model = $this->loadModel($id);
        $actor = My::app()->getActor();
        $rules = $this->modelRules();
        $role = $rules[My::app()->getActionName()];
        return $model->chechAccess($actor, $role);
    }
}

Вы можете вызвать метод SomeController :: checkModelAccess ($ id), когда создаете свои меню и решаете, показывать ли какую-либо ссылку.

Георгий Советов
источник
Я прошу прощения за PHP.
Георгий Советов
1

И в модели, и в представлении

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

(как, скажем, кнопка «Удалить» должна быть показана людям с соответствующими разрешениями)

В модели - потому что ваше приложение, вероятно, имеет какой-то API, верно? API также должен проверять разрешения и, возможно, повторно использовать модель.

(как, скажем, у вас есть кнопка «Удалить» в пользовательском интерфейсе и метод «http: / server / API / DeleteEntry / 123» API одновременно)

Jitbit
источник
Почему вы выбрали модель вместо контроллера?
Флим
Не уверен, почему вид, модель, а не в контроллере, где большую часть времени это делается.
VP.
@VP контроллер не имеет возможности показывать / скрывать элементы пользовательского интерфейса (кроме передачи bool-var TO THE VIEW)
jitbit
Я не знаю, везде обычно делается на уровне контроллера, поэтому мне было любопытно.
VP.
0

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

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

  • слой представления должен скрывать возможности редактирования
  • Если функция редактирования вызывается в любом случае, это может / должно быть обнаружено (частями приложения бизнес-уровня, а не частью его домена - TrainEditor, а не Train) и, вероятно, вызвать исключение
  • Уровень доступа к данным также может проверять записи, но для более сложных видов разрешений, которые быстро требуют слишком больших знаний бизнес-уровня, чтобы быть хорошей идеей.

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

Патрик
источник