Потребовалось мне добавить пару ролей в проект Yii2. Задача стояла распределить пользователей на несколько групп. Для этих целей можно использовать RBAC, однако это решение в данном случае было слишком избыточно - требовалось что-то гораздо проще. Т.к. такая задача возникает довольно часто, напишу как можно справиться с ней всего парой строчек кода..
Начнем с модели User. Данные в моем случае хранятся в бд, поэтому User унаследован от ActiveRecord и реализует IdentityInterface.
Добавляем в модель (файл app\models\User.php) необходимые роли и метод проверки ролей can:
class User extends \yii\db\ActiveRecord implements \yii\web\IdentityInterface { const ROLE_ADMIN = 'root'; const ROLE_USER = 'user'; const ROLE_ATTORNEY = 'attorney'; .. public function can($role) { return $this->role == $role; } }
Далее добавляем колонку role в таблицу user, использя для этого либо миграции, либо напрямую редактируя таблицу, в зависимости от этапа разработки.
Теперь, создаем новый фильтр AccessRule (файл app\components\AccessRule.php), который наследуется от базового и переопределяет метод match:
<?php namespace app\components; class AccessRule extends \yii\filters\AccessRule { /** * @inheritdoc */ protected function matchRole($user) { if (empty($this->roles)) { return true; } foreach ($this->roles as $role) { if ($role === '?') { if ($user->getIsGuest()) { return true; } } elseif ($role === '@') { if (!$user->getIsGuest()) { return true; } } elseif ($user->can($role)) { return true; } elseif ($identity = $user->getIdentity()) { if ($identity->can($role)) { return true; } } } return false; } }
Здесь мы добавили в конец проверок, вызов метода can из модели User который мы определили ранее.
Вот и всё, теперь нам осталось в соответствующем контроллере, переопределить namespace AccessRule на наш и можно использовать правила фильтрации как и раньше, но уже с нашими ролями:
namespace app\controllers; use Yii; use yii\filters\AccessControl; use app\components\AccessRule; .. use app\models\User; class SiteController extends Controller { /** * @inheritdoc */ public function behaviors() { return [ 'access' => [ 'class' => AccessControl::className(), 'ruleConfig' => [ 'class' => AccessRule::className(), ], 'only' => ['test'], 'rules' => [ [ 'actions' => ['test'], 'allow' => true, 'roles' => [User::ROLE_ADMIN], ], ], ], ]; }
Как видите, задача решается довольно просто. Слава фреймворку Yii 2 и его разработчикам 😀
Комментарии (14)
--------------
Вау! Да ВЫ просто ВОЛШЕБНИК))) спасли меня!!!!!
--------------
Еще вопрос: вместо конструкции
if (Yii::$app->user->getIdentity()) { .... }
можно ведь использовать так?! или не правильно? вроде работает..
if (!Yii::$app->user->isGuest) { .... }
роли же присваются только авторизованным пользователям? вот и подумал, что проверять гость или нет... если авторизован, тога проверять роли.
И да, еще вопрос... если использовать yii basic и создать модули user и admin и для каждого модуля свой identity (model user) - то ваш код отказывается работать... начинает обращаться к identity который прописан в конфиге. А я хотел разделить регистрацию/авторизацию как например можно было сделать в yii1 - а вот как в yii2 так сделать? чтобы например создал 3 модуля админа, модератора, редактора.. и чтобы у каждого модуля был своя форма авторизации и регистрации?
Привет,
Yii::$app->user->isGuest
в коде выглядит так
...
public function getIsGuest()
{
return $this->getIdentity() === null;
}
...
смотри тут https://github.com/yiisoft/yii2
т.е. можно использовать и так, и так
Спасибо, я все понял!
Спасибо за такой простой хак. Есть вопрос: а как можно проверять во вьюшках? например чтобы выводить для каждой роли свою инфу...? Пробовал так
--
User::can(User::ROLE_USER) - но пишет что не известно свойство.
а так вообще не видит метода
-
Yii::$app->user->identity->can(USER::ROLE_ADMIN);
как мне по вашему способу ролей проверять эти роли во вьюшках?
пробовал в модель User добавить геттеры и сеттеры, но не робит.
public static function getRole(){
return !empty($this->role) ? $this->role : "";
}
public static function setRole(){
return $this->role = $role;
}
Подскажите пожалуйста?!
Почему такие извращения? дело в том,что у меня был самописный проект..и каждый тип пользователя ( админ, редактор, модератор) находятся в отдельных таблицах. На yii1 я быстро перешел... а вот на yii2 никак не могу решить проблему.. Как при авторизации понять, в какой таблице искать? Когда все пользователи в одной таблице находятся- это понятно... А вот если в разных? Может вопрос кажется глупым, но я уже мозг сломал свой..никак не получается реализовать. Разные identity почему то не работают... Как бы работают...авторизация проходит..но вот потом почему то подключается identity который указан в конфиге.... Брр... сумбур уже в голове. А так хочется перелезть с yii1 на yii2
Если еще не знаешь, то на гитхабе в официальном репозитории есть перевод доков на русский, там неплохо все описано, в том числе роли (через RBAC): https://github.com/yiisoft/yii2/blob/master/docs/guide-ru/security-authorization.md
Identity класс не обязательно должен наследоваться от ActiveRecord, т.е. ты можешь сделать внутри него кастомную логику которая будет обращаться к разным таблицам. Кроме того, если там совсем все сложно, то можно вообще отказаться от Identity, определить свою модель аутентификации и использовать её, но это на крайний случай, т.к. любые не стандартные решения, в большинстве случаев затратны в плане дальнейшей поддержки.
не надо ляля, в дках ничего толком не описано и нет примеров. Запарили советовать всякую шляпу
Да ладно :) просто ссылка на страницу доков поменялась (уже обновил), вот актуальная:
https://github.com/yiisoft/yii2/blob/master/docs/guide-ru/security-authorization.md
Все там есть, с примерами, подробным объяснением и умными словами по которым можно погуглить.
или создать модели для каждой талицы (админ, редактор, пользователь) и уже реализовать identity и работать с конкретной моделью? так?
Когда у тебя 3и таблицы, то Identity должен понимать из какой таблицы (модели) ему брать информацию. У него есть обязательные методы:
..
public function getId()
public static function findIdentity($id)
..
так вот getId у тебя должен возвращать уникальный id для 3х таблиц, а findIdentity находить нужный по id
не проверял, но идея кодом будет выглядеть примерно так
namespace app\models;
class Admin extends ActiveRecord {
...
}
class Editor extends ActiveRecord {
...
}
namespace app\models;
class User implements IdentityInterface
{
public $id;
public $model;
public static function findIdentity($id)
{
$ret = null;
list($model, $id) = explode('::', $id);
if (class_exists($model)) {
$ret = new static;
$ret->id = $id;
$ret->model = $model;
}
return $ret;
}
public static function findIdentityByAccessToken($token, $type = null)
{
return null;
}
public function getId()
{
return $this->model.'::'.$this->id;
}
public function getAuthKey()
{
$model = $this->getModelInstance()->findOne($id);
return $model ? $model->authKey : null;
}
public function validateAuthKey($authKey)
{
return $this->getAuthKey() === $authKey;
}
public function getModelInstance(){
$model = $this->model;
return new $model;
}
}
Пример дальнейшего использования:
// логин в контролере
$identity = Yii::$app->findIdentity('\app\models\Admin::1');
Yii::$app->user->login($identity);
// вывод имени пользователя во вьюхе
$identity = Yii::$app->user->getIdentity();
if ($identity) {
$identity->getModelInstance()->username;
}
Думаю, идея понятна, но надо проверять..
Другой способ, это завести еще одну таблицу, например так:
======================
табл user
-------
id: 1
model: admin_user
model_id:1
-------
id: 2
model: editor_user
model_id:1
======================
табл admin_user
-------
id: 1
username: Admin
======================
и определить в классе user метод getModel, который будет брать запись из зависимой таблицы на основе полей model и model_id
Еще один способ, отказаться вообще от компонента \yii\web\User и написать свой компонент аутентификации со своим блэкджеком и дальше использовать его.
через RBAC не хочется, слишком замудренно. Для моего проекта не нужно такой функционал. Поэтому меня и заинтересовала твоя статья. Вот бы еще примерчик того, как с разных таблиц получать данные при авторизации.
Я вот представляю так: что делаем свою логику , а уже потом просто вызвать метод компанента из коробки - Yii::$app->getUser()->login($user) ..... или не так? но опять же, все равно нужно наследовать identity -
--- т.е. ты можешь сделать внутри него кастомную логику которая будет обращаться к разным таблицам --
Вроде как понимаю, и с другой стороны все равно не пойму..... Краткий примерчик бы, чтоб глянуть.
спасибо за помощь! я решил проблему))) оказалось намного все проще с вашим приведенным в статье кодом))) а я начал велосипед изобретать))
Привет, попробуй так во вьюшке:
if (Yii::$app->user->getIdentity()) {
if (Yii::$app->user->getIdentity()->can(\app\models\User::ROLE_USER)) {
// разрешено
}
}