23 окт. 2015 г., 11:02:43 Yii2 yii2 советы rbac 8 Комментариев
Много разных вариантов реализации ролей доступа мной было испробовано, пока я не нашел идеально подходящий для меня.
Первое, что мне не нравится в RBAC реализованный в Yii2, это возможность использовать несколько ролей. На самом деле, при правильно реализованной иерархической структуре достаточно одной роли.
Второе, что мне не нравится, это хранение назначений отдельно, т.е. связывание роли с пользователем. И это в коробке вообще никак не отключается. Если нужно действительно это отключить, чтобы случайно другой программист не заюзал - нужно переопределять менаджер авторизации и вешать всякие throw и прочее. Роль, имхо, должна указываться в таблице с пользователем, потому что: если нужно делать дамп, то если назначения хранятся в ФС - придется делать дамп и связей; а если назначения хранятся в БД (а хранить их в БД нет никакого смысла вообще), то такое тяжело поддерживать, если вдруг структура ролей будет изменена; и потом, если хранить назначения с ролями отдельно, то у людей получается жуткий говнокод, если нужно иметь возможность "видеть" роль пользователя и менять её динамически. Поэтому, делать нужно правильно изначально: все роли, правила, иерархию наследования храним в ФС, а связываем все это дело, через поле role в таблице с пользователями.
С реализацией все очень просто.
Добавление поля
Во-первых, нужно добавить поле role
в таблицу с пользователями. Лучше всего, чтобы role
имела тип VARCHAR(64) NOT NULL
, но дело ваше. Это не принципиально по сути.
Объявляем роли в модели пользователей
const ROLE_SUPERUSER = 'superuser'; const ROLE_REGISTERED = 'registered'; const ROLE_GUEST = 'guest'; /** * Возвращает массив всех доступных ролей. * @return array */ static public function roleArray() { return [ self::ROLE_SUPERUSER, self::ROLE_REGISTERED, self::ROLE_GUEST, ]; }
Настраиваем AuthManager в конфигурации приложения
use app\models\User; 'authManager' => [ 'class' => 'yii\rbac\PhpManager', 'itemFile' => '@app/rbac/items.php', 'ruleFile' => '@app/rbac/rules.php', 'assignmentFile' => '@app/rbac/assignments.php', // назначения придется указать, потому что того требуют каноны церкви 'defaultRoles' => User::roleArray(), ],
В defaultRoles
мы указали список всех доступных ролей. Это нужно для того, чтобы менеджер авторизации знал о их существовании. А чтобы он не присваивал их кому попало, нужно реализовать правило на проверку принадлежности к роли у пользователя, который такое поведение будет исключать.
Добавляем правило на проверку принадлежности к роли у пользователя
Теперь нужно создать класс UserRoleRule
в app/rbac
, правило которого будет проверять принадлежность пользователя к роли.
use Yii; use yii\rbac\Rule; use app\models\User class UserRoleRule extends Rule { /** * @inheritdoc */ public $name = 'userRole'; private $_assignments = []; /** * @inheritdoc */ public function execute($user, $item, $params) { if ($role = $this->userRole($user)) { switch ($item->name) { case User::ROLE_SUPERUSER: return $role == User::ROLE_SUPERUSER; case User::ROLE_REGISTERED: return $role == User::ROLE_SUPERUSER || $role == User::ROLE_REGISTERED; case User::ROLE_GUEST: return in_array($role, [User::ROLE_SUPERUSER, User::ROLE_REGISTERED, User::ROLE_GUEST]); } } return false; } /** * @param integer|null $userId ID of user. * @return string|false */ protected function userRole($userId) { $user = Yii::$app->user; if ($userId === null) { if ($user->isGuest) { return Users::ROLE_GUEST; } return false; } if (!isset($this->_assignments[$userId])) { $role = false; if (!$user->isGuest && $user->id == $userId) { $role = $user->role; } elseif ($user->isGuest || $user->id != $userId) { $role = User::getRoleOfUser($userId); } $this->_assignments[$userId] = $role; } return $this->_assignments[$userId]; } }
Метод userRole
реализует получение роли пользователя даже в случае, если вы проверяете не только текущего пользователя, но и какого-то другого. Этот метод обращается к User::getRoleOfUser()
для получения роли пользователя. Реализация данного метода примерно такая:
/** * Возвращает роль пользователя по его ID в случае успеха и `false` в случае неудачи. * @param integer $id ID пользователя. * @return string|false */ static public function getRoleOfUser($id) { return (new Query) ->select('role') ->from(self::tableName()) ->where(['id' => $id]) ->scalar(); }
Также, компонент user
в методе userRole
обращается к свойству role
, которое реализовано через геттер в компоненте yii\web\User
. Вам нужно либо добавить данный метод в уже унаследованный у вас класс yii\web\User
, либо унаследоваться и потом добавить, либо добавить в UserRoleRule
, чего я не рекомендую. Реализация метода примерно такая:
/** * Возвращает роль пользователя или `null`. * @return string|null */ public function getRole() { $identity = $this->getIdentity(); return $identity !== null ? $identity->role : null; }
Инициализируем RBAC
Т.к. у нас все уже подготовлено, то можно приступить к добавлению ролей, правил и т.д.
В консольном контроллере создаем действие actionInitRbac
:
use Yii; use app\rbac\UserRoleRule; use app\models\User; public function actionInitRbac() { $auth = Yii::$app->getAuthManager(); $auth->removeAll(); $userRoleRule = new UserRoleRule; $auth->add($userRoleRule); $superuser = $auth->createRole(User::ROLE_SUPERUSER); $superuser->ruleName = $userRoleRule->name; $auth->add($superuser); $registered = $auth->createRole(User::ROLE_REGISTERED); $registered->ruleName = $userRoleRule->name; $auth->add($registered); $guest = $auth->createRole(User::ROLE_GUEST); $guest->ruleName = $userRoleRule->name; $auth->add($guest); $auth->addChild($registered, $guest); $auth->addChild($superuser, $registered); }
Иерархия ролей получилась следующей:
- superuser - | - registered - - | - guest
Инициализируем
./yii controller-name/init-rbac
Вот и все. Теперь, достаточно указывать в поле role
таблицы пользователя роль пользователя. Проверка прав доступа не изменилась.
Данный метод работает прекрасно без нареканий. Я им полностью доволен.
UPD 26.11.2015 16:26: Добавлена роль гость для общей картины.
Комментарии [8]
Новый комментарий