Yii2: Простая реализация RBAC с двумя ролями

Один из самых часто встречающихся вопросов по Yii 2.0 — реализация управления доступом на основе ролей. Поддержка RBAC встроена в Yii2, но она может быть сложновата для реализации начинающими разработчиками, впервые столкнувшимися с этим вопросом при знакомстве с Yii 2.0.

Часто бывает достаточно всего двух ролей: Пользователь и Администратор. Эта реализация поможет понять как работает механизм RBAC в Yii2 и может стать отправной точкой для дальнейшего расширения функционала управления доступом на основе ролей.

Большая часть работы уже проделана разработчиками Yii 2.0. Что осталось сделать:

  1. Добавить константу  для роли Администратора в модуль User
  2. Добавить эту константу в перечень значений роли пользователей
  3. Добавить к модели User статичный метод isUserAdmin
  4. Добавить к модели LoginForm метод loginAdmin
  5. Изменить контроллер backend так, чтобы он использовал метод loginAdmin при входе пользователя.
  6. Добавим правило доступа, ограничивающее доступ к странице about для всех, кроме Администратора.

За основу возьмем приложение advanced. Про установку его из стандартной поставки Yii2 можно узнать в официальном руководстве.

Добавим константу для роли Администратора

В начало модели User, common/models/User.php, добавим:

const ROLE_ADMIN = 20;

Мы будем использовать константу для хранения значения 20, указывающего на роль Администратора. Если значение поля role будет равно 20, значит субъект получает права Администратора. По-умолчанию значение этого поля будет равно 10, что означает права Пользователя для данного субъекта. На данном этапе можно изменить значение данного поля прямиком в базе данных, например, используя PhpMyAdmin.

Адаптируем правила валидации модели User

Внесем изменения в метод Rules модели User:

['role', 'in', 'range' => [self::ROLE_USER, self::ROLE_ADMIN]],

Просто ограничим значение поля роли двумя значениями, хранящимися в константах.

Добавим метод isUserAdmin

Файл common/models/User.php:

public static function isUserAdmin($username)
{
    if (static::findOne(['username' => $username, 'role' => self::ROLE_ADMIN]))
    {
        return true;
    } else {
        return false;
    }
}

Имя метода говорит само за себя. Используя метод findOne ищем запись с соответствующим именем и ролью Администратора. Если запись не будет найдена, возвращаем false.

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

Организуем вход для Администратора

Добавим метод loginAdmin в модель commom/models/LoginForm.php:

public function loginAdmin()
{
    if ($this->validate() && User::isUserAdmin($this->username)) {
        return Yii::$app->user->login($this->getUser(), $this->rememberMe ? 3600 * 24 * 30 : 0);
    } else {
        return false;
    }
}

Просто добавим еще одно условие. Теперь не только валидация должна пройти успешно, но и метод User::isUserAdmin($this->username) должен вернуть true.

Настроим форму входа в backend

Внесем изменения в файл backend\controllers\SiteController.php:

public function actionLogin()
{
   if (!\Yii::$app->user->isGuest) {
      return $this->goHome();
   }

   $model = new LoginForm();
   if ($model->load(Yii::$app->request->post()) && $model->loginAdmin()) {
      return $this->goBack();
   } else {
       return $this->render('login', [
          'model' => $model,
       ]);
   }
}

Изменение совсем небольшое, используем метод loginAdmin вместо login.

Теперь для входа в backend пользователю необходимо иметь роль Администратора, то есть значение роли должно равняться значению константы ROLE_ADMIN модели User.

Настал момент ограничить доступ к методам контроллера. Проще всего это реализовать используя поведения и метод matchCallback в правилах.

Ограничиваем доступ к методу about приложения frontend

В начале файла frontend/controllers/SiteController.php подключим модель User:

use common\models\User;

В поведение, метод behaviors, в том же файле frontend/controllers/SiteController.php  добавим:

public function behaviors()
{
   return [
       'access' => [
           'class' => AccessControl::className(),
           'only' => ['logout', 'signup', 'about'],
           'rules' => [
               [
                   'actions' => ['signup'],
                   'allow' => true,
                   'roles' => ['?'],
               ],
               [
                   'actions' => ['logout'],
                   'allow' => true,
                   'roles' => ['@'],
               ],
               [
                   'actions' => ['about'],
                   'allow' => true,
                   'roles' => ['@'],
                   'matchCallback' => function ($rule, $action) {
                       return User::isUserAdmin(Yii::$app->user->identity->username);
                   }
               ],
           ],
       ],
       'verbs' => [
           'class' => VerbFilter::className(),
           'actions' => [
               'logout' => ['post'],
           ],
       ],
   ];
}

Что нового? Мы изменили строку:

 'only' => ['logout', 'signup', 'about'],

Теперь поведение распространяется и на экшн about. Для него и добавили правило в массиве:

               [
                   'actions' => ['about'],
                   'allow' => true,
                   'roles' => ['@'],
                   'matchCallback' => function ($rule, $action) {
                       return User::isUserAdmin(Yii::$app->user->identity->username);
                   }
               ],

Это правило распространяется только на метод action. Метод matchCallback используется для проверки текущего пользователя Yii::$app->user->identity->username на соответствие роли Администратора. В случае, если роль пользователя не Администратор, будет выдано предупреждение о том, что доступ запрещен.

Очевидно, что вход пользователя в систему необходим для определения его роли. И доступ не будет разрешен для пользователя не вошедшего в систему.

Такая методика будет работать с любым контроллером, где в поведении behaviors правила доступа описаны подобным образом. Данная простая реализация RBAC может быть использована в любом эшне frontend или backend приложений.

Подведем итоги

У нас получилась простая, минимальная реализация RBAC на Yii2, ее вполне достаточно во многих случаях. Yii 2.0 «из коробки» уже имеет все, что нужно для начала работы. Мало какие еще php фреймворки так дружелюбны как к начинающим, так и к опытным разработчикам. Мы добились нужного результата всего несколькими строчками кода.

Если вы хотите получить пользовательский интерфейс для управления ролями пользователей,  можно создать crud для модели User в backend, используя замечательный генератор кода Gii, встроенный в Yii. Главное правильно использовать пространства имен: основная модель User находится в common\models.

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

12 thoughts on “Yii2: Простая реализация RBAC с двумя ролями

  1. ученик

    Доброго времени суток! Очень помогла ваша статья настроить разделение прав по ролям.
    Однако у меня в SiteController не виден класс User, и пришлось по другому реализовать проверку.
    Как найти ошибку, почему у меня не виден класс User?

    1. nix Автор записи

      Нужно явно указать пространство имен для класса User:
      use app\common\models\User;
      или
      app\common\models\User::isUserAdmin(Yii::$app->user->identity->username);

      1. Егор

        \common\models\User::isUserAdmin(Yii::$app->user->identity->username);
        Вот этим длинным запросом пользуюсь, чтобы детектить админа во view.
        Хочу добавить не статический метод для проверки на админа, чтобы было доступно Yii::$app->user->isAdmin()

        хотя можно и Yii::$app->user->identity->role == \common\models\User::ROLE_ADMIN

        Но может лучше использовать RBAC? Подсакжите)

  2. илья

    Привет!
    (сборка basic Yii2.0.7) Два вопроса:

    У вас написано:
    Внесем изменения в метод Rules модели User:
    [‘role’, ‘in’, ‘range’ => [self::ROLE_USER, self::ROLE_ADMIN]],
    1. Не нашел в User метода Rules (есть ли там вообще валидация?)
    2. Не нашел объявления self::ROLE_USER

    1. Никита

      Мне кажется, что причина в приложении «basic».
      Я у себя тоже не нашёл этого метода.
      Шаг в сторону от мануала и уже всё не работает и ничего не получается починить )))

      А для начинающих было бы здорово сделать точно такой же мануал для приложения «basic» :)

      Опытные люди итак разберутся, а начинающим рекомендуют не лезть в advanced…

    2. Егор

      1. Видимо метод rules для basic нужно создавать вручную, а
      2. константы ROLE_USER тоже нет в advanced, думается что её тоже можно добавить вручную

      Пробуйте, будут ошибки — гугл поможет

  3. ihor

    А где в базе данных хранится идентификатор админа (20)? В таблице user нет поля role!

  4. игорь

    Где в базе данных хранится идентификатор админа (20)? Ведь в таблице user нет поля role.

  5. cache0

    добавте в базу колонку role

    и в файл

    const STATUS_DELETED = 0;
    const STATUS_ACTIVE = 10;
    const ROLE_USER = 10;
    const ROLE_ADMIN = 20;

  6. Andrey

    А для basic нельзя было сделать. Особенно, если вы начали говорить о новичках… Ладно, буду разбираться так.. Самое сложное для меня было Рег/авт/аут, с чем я разобрался

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *