Авторизация - это процесс проверки того, что у пользователя достаточно разрешения на выполнение каких-либо действий. Yii предоставляет два метода авторизации: Фильтр управления доступом (ACF) и Контроль доступа на основе ролей (RBAC).

Фильтр контроля доступа

Фильтр контроля доступа (ACF) - это простой метод авторизации, реализованный в виде yii\filters\AccessControl, который лучше всего использовать для приложений, которым требуется только простой контроль доступа. Как видно из его названия, ACF - это фильтр действий, который можно использовать в контроллере или модуле. Пока пользователь запрашивает выполнение действия, ACF проверит список правил доступа, чтобы определить, разрешен ли пользователю доступ к запрошенному действию.

В приведенном ниже коде показано, как использовать ACF в контроллере site:

use yii\web\Controller;
use yii\filters\AccessControl;

class SiteController extends Controller
{
    public function behaviors()
    {
        return [
            'access' => [
                'class' => AccessControl::className(),
                'only' => ['login', 'logout', 'signup'],
                'rules' => [
                    [
                        'allow' => true,
                        'actions' => ['login', 'signup'],
                        'roles' => ['?'],
                    ],
                    [
                        'allow' => true,
                        'actions' => ['logout'],
                        'roles' => ['@'],
                    ],
                ],
            ],
        ];
    }
    // ...
}

В коде выше ACF привязан к контроллеру site как к поведению. Это типичный способ использования фильтра действий. Опция only указывает, что ACF следует применять только к действиям login, logout и signup. Все остальные действия в контроллере site не подлежат контролю доступа. Опция rules содержит правила доступа, которые выглядят следующим образом:

  • Разрешить всем гостям (еще не прошедшим проверку) пользователям доступ к действиям login и signup. Опция ролей содержит вопросительный знак ? который представляет собой специальный токен, представляющий «гостевые пользователи».
  • Разрешить пользователям, прошедшим проверку подлинности, получить доступ к действию logout. Символ @ - это еще один специальный токен, представляющий «прошедших проверку подлинности пользователей».

ACF выполняет проверку авторизации, просматривая правила доступа по одному сверху донизу, пока не найдет правило, соответствующее текущему контексту выполнения. Значение allow правила сопоставления затем будет использоваться для определения того, разрешен пользователь или нет. Если ни одно из правил не соответствует, это значит, что пользователь НЕ авторизован, и ACF прекратит выполнение дальнейших действий.

Когда ACF определяет, что пользователь не авторизован для доступа к текущему действию, по умолчанию он принимает следующую меру:

  • Если пользователь является гостем, он вызывает yii\web\User::loginRequired(), чтобы перенаправить браузер пользователя на страницу входа.
  • Если пользователь уже аутентифицирован, он будет вызывать yii\web\ForbiddenHttpException.

Вы можете настроить это поведение, настроив свойство yii\filters\AccessControl::$denyCallback следующим образом:

[
    'class' => AccessControl::className(),
    ...
    'denyCallback' => function ($rule, $action) {
        throw new \Exception('You are not allowed to access this page');
    }
]

Правила доступа поддерживают множество опций. Ниже приведено краткое описание поддерживаемых параметров. Вы также можете расширить yii\filters\AccessRule, чтобы создать свои собственные классы правил доступа.

  • allow: указывает, является ли это правилом «allow» или «deny».
  • actions: указывает, какие действия соответствует этому правилу. Это должен быть массив идентификаторов действий. Сравнение чувствительно к регистру. Если этот параметр пуст или не задан, это означает, что правило применяется ко всем действиям.
  • controllers: указывает, с какими контроллерами это правило согласуется. Это должен быть массив идентификаторов контроллеров. Каждому идентификатору контроллера присваивается идентификатор модуля (если он есть). Сравнение чувствительно к регистру. Если этот параметр пуст или не задан, это означает, что правило применяется ко всем контроллерам.
  • roles: указывает, какие пользовательские роли соответствуют этому правилу. Две специальные роли распознаются и проверяются через yii\web\User::$isGuest:
    • ?: Совпадает с гостевым пользователем (еще не аутентифицирован)
    • @: Соответствует аутентифицированному пользователю 
    • Использование других имен ролей вызовет вызов yii\web\User::can(), что требует включения RBAC (будет описано в следующем подразделе). Если этот параметр пуст или не задан, это означает, что это правило применяется ко всем ролям.
  • ips: указывает, каким IP-адресам клиента соответствует это правило. IP-адрес может содержать подстановочный знак * в конце, чтобы он соответствовал IP-адресам с одинаковым префиксом. Например, '192.168. *' Соответствует всем IP-адресам в сегменте '192.168.'. Если этот параметр пуст или не задан, это означает, что это правило применяется ко всем IP-адресам.
  • verbs: определяет, какой метод запроса (например, GET, POST) соответствует этому правилу. Сравнение нечувствительно к регистру.
  • matchCallback: указывает вызываемый PHP, который должен быть вызван, чтобы определить, должно ли это правило применяться.
  • denyCallback: указывает вызываемый PHP, который должен быть вызван, когда это правило будет запрещать доступ.

Ниже приведен пример, показывающий, как использовать параметр matchCallback, который позволяет вам писать произвольную логику проверки доступа:

use yii\filters\AccessControl;

class SiteController extends Controller
{
    public function behaviors()
    {
        return [
            'access' => [
                'class' => AccessControl::className(),
                'only' => ['special-callback'],
                'rules' => [
                    [
                        'actions' => ['special-callback'],
                        'allow' => true,
                        'matchCallback' => function ($rule, $action) {
                            return date('d-m') === '31-10';
                        }
                    ],
                ],
            ],
        ];
    }

    // Match callback called! This page can be accessed only each October 31st
    public function actionSpecialCallback()
    {
        return $this->render('happy-halloween');
    }
}

Управление доступом на основе ролей (RBAC)

Управление доступом на основе ролей (RBAC) обеспечивает простой, но мощный централизованный контроль доступа. Yii реализует общий иерархический RBAC, следуя модели NIST RBAC. Он обеспечивает функциональность RBAC с помощью компонента приложения authManager. Использование RBAC включает в себя две части работы. Первая часть - это создание данных авторизации RBAC, а вторая часть - использование данных авторизации для выполнения проверки доступа в тех местах, где это необходимо. Чтобы облегчить наше описание, мы сначала представим некоторые базовые концепции RBAC.

Базовые концепты

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

С каждой ролью или разрешением может существовать правило. Правило представляет собой фрагмент кода, который будет выполняться во время проверки доступа, чтобы определить, относится ли соответствующая роль или разрешение к текущему пользователю. Например, у разрешения «обновление записи» может быть правило, которое проверяет, является ли текущий пользователь создателем сообщения. Во время проверки доступа, если пользователь НЕ является создателем сообщения, он/она будет считаться не имеющим разрешения «обновить сообщение».

Обе роли и разрешения могут быть организованы в иерархии. В частности, роль может состоять из других ролей или разрешений; И разрешение может состоять из других разрешений. Yii реализует иерархию частичного порядка, которая включает более специальную иерархию дерева. Хотя роль может содержать разрешение, это не так.

Настройка RBAC

Прежде чем мы начнем определять данные авторизации и выполнить проверку доступа, нам необходимо настроить компонент приложения authManager. Yii предоставляет два типа менеджеров авторизации: yii\rbac\PhpManager и yii\rbac\DbManager. Первый использует файл сценария PHP для хранения данных авторизации, а второй хранит данные авторизации в базе данных. Вы можете использовать первый, если ваше приложение не требует очень динамической роли и управления разрешениями.

Использование PhpManager

В следующем коде показано, как настроить authManager в конфигурации приложения с помощью класса yii\rbac\PhpManager:

return [
    // ...
    'components' => [
        'authManager' => [
            'class' => 'yii\rbac\PhpManager',
        ],
        // ...
    ],
];

Теперь к authManager можно получить доступ через \Yii::$app->authManager.

По умолчанию yii\rbac\PhpManager сохраняет данные RBAC в файлах в каталоге @app/rbac. Убедитесь, что каталог и все файлы в нем доступны для записи процессом веб-сервера, если иерархия разрешений должна быть изменена онлайн.

Использование DbManager

В следующем коде показано, как настроить authManager в конфигурации приложения с помощью класса yii\rbac\DbManager:

return [
    // ...
    'components' => [
        'authManager' => [
            'class' => 'yii\rbac\DbManager',
        ],
        // ...
    ],
];

DbManager использует четыре таблицы базы данных для хранения своих данных:

  • itemTable: таблица для хранения элементов авторизации. По умолчанию используется значение "auth_item»"
  • itemChildTable: таблица для хранения иерархии элементов авторизации. По умолчанию используется "auth_item_child".
  • assignTable: таблица для хранения назначений элементов полномочий. По умолчанию используется значение "auth_assignment".
  • ruleTable: таблица для хранения правил. По умолчанию используется значение "auth_rule".

Прежде чем вы сможете продолжить, вам нужно создать эти таблицы в базе данных. Для этого вы можете использовать миграцию, хранящуюся в @yii/rbac/migrations:

yii migrate --migrationPath=@yii/rbac/migrations

Теперь к authManager можно получить доступ через \Yii::$app->authManager.

Данные авторизации здания

Данные авторизации здания сводятся к следующим задачам:

  • Определение ролей и разрешений;
  • Установление отношений между ролями и разрешениями;
  • Определение правил;
  • Сопоставление правил с ролями и разрешениями;
  • Назначение ролей пользователям.

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

Если ваша иерархия разрешений вообще не изменяется, и у вас есть фиксированное число пользователей, вы можете создать консольную команду, которая будет инициализировать данные авторизации один раз через API, предлагаемые authManager:

<?php
namespace app\commands;

use Yii;
use yii\console\Controller;

class RbacController extends Controller
{
    public function actionInit()
    {
        $auth = Yii::$app->authManager;

        // add "createPost" permission
        $createPost = $auth->createPermission('createPost');
        $createPost->description = 'Create a post';
        $auth->add($createPost);

        // add "updatePost" permission
        $updatePost = $auth->createPermission('updatePost');
        $updatePost->description = 'Update post';
        $auth->add($updatePost);

        // add "author" role and give this role the "createPost" permission
        $author = $auth->createRole('author');
        $auth->add($author);
        $auth->addChild($author, $createPost);

        // add "admin" role and give this role the "updatePost" permission
        // as well as the permissions of the "author" role
        $admin = $auth->createRole('admin');
        $auth->add($admin);
        $auth->addChild($admin, $updatePost);
        $auth->addChild($admin, $author);

        // Assign roles to users. 1 and 2 are IDs returned by IdentityInterface::getId()
        // usually implemented in your User model.
        $auth->assign($author, 2);
        $auth->assign($admin, 1);
    }
}

 Автор может создать сообщение, администратор может обновить сообщение и сделать все, что может сделать автор.

Если ваше приложение разрешает регистрацию пользователей, вам нужно назначить роли этим новым пользователям один раз. Например, чтобы все зарегистрированные пользователи стали авторами вашего расширенного шаблона проекта, вам необходимо изменить интерфейс frontend\models\SignupForm::signup() следующим образом:

public function signup()
{
    if ($this->validate()) {
        $user = new User();
        $user->username = $this->username;
        $user->email = $this->email;
        $user->setPassword($this->password);
        $user->generateAuthKey();
        $user->save(false);

        // the following three lines were added:
        $auth = \Yii::$app->authManager;
        $authorRole = $auth->getRole('author');
        $auth->assign($authorRole, $user->getId());

        return $user;
    }

    return null;
}

Для приложений, которым требуется сложное управление доступом с динамически обновляемыми данными авторизации, возможно, потребуется разработать специальные пользовательские интерфейсы (например, панель администратора) с использованием API, предлагаемых authManager.

Использование правил

Как уже упоминалось, правила добавляют дополнительное ограничение ролям и разрешениям. Правило - это класс, начинающийся с yii\rbac\Rule. Он должен реализовать метод execute(). В иерархии, которую мы создали ранее, автор не может редактировать свой пост. Давайте исправим это. Сначала нам нужно правило, чтобы убедиться, что пользователь является автором сообщения:

namespace app\rbac;

use yii\rbac\Rule;

/**
 * Checks if authorID matches user passed via params
 */
class AuthorRule extends Rule
{
    public $name = 'isAuthor';

    /**
     * @param string|int $user the user ID.
     * @param Item $item the role or permission that this rule is associated with
     * @param array $params parameters passed to ManagerInterface::checkAccess().
     * @return bool a value indicating whether the rule permits the role or permission it is associated with.
     */
    public function execute($user, $item, $params)
    {
        return isset($params['post']) ? $params['post']->createdBy == $user : false;
    }
}

Вышеуказанное правило проверяет, создан ли post в $user. Мы создадим специальное разрешение updateOwnPost в команде, которую мы использовали ранее:

$auth = Yii::$app->authManager;

// add the rule
$rule = new \app\rbac\AuthorRule;
$auth->add($rule);

// add the "updateOwnPost" permission and associate the rule with it.
$updateOwnPost = $auth->createPermission('updateOwnPost');
$updateOwnPost->description = 'Update own post';
$updateOwnPost->ruleName = $rule->name;
$auth->add($updateOwnPost);

// "updateOwnPost" will be used from "updatePost"
$auth->addChild($updateOwnPost, $updatePost);

// allow "author" to update their own posts
$auth->addChild($author, $updateOwnPost);

Проверка доступа

Когда данные авторизации готовы, проверка доступа так же просто, как вызов метода yii\rbac\ManagerInterface::checkAccess(). Поскольку большая часть проверки доступа относится к текущему пользователю, для удобства Yii предоставляет метод ярлыка yii\web\User::can(), который можно использовать следующим образом:

if (\Yii::$app->user->can('createPost')) {
    // create post
}

Чтобы проверить, может ли пользователь обновлять сообщение, нам нужно передать дополнительный параметр, который требуется для AuthorRule:

if (\Yii::$app->user->can('updatePost', ['post' => $post])) {
    // update post
}

Внутри вашего контроллера есть несколько способов реализации авторизации. Если вам нужны подробные разрешения, разделяющие доступ к добавлению и удалению, вам нужно проверить доступ для каждого действия. Вы можете использовать указанное выше условие в каждом методе действия или использовать yii\filters\AccessControl:

public function behaviors()
{
    return [
        'access' => [
            'class' => AccessControl::className(),
            'rules' => [
                [
                    'allow' => true,
                    'actions' => ['index'],
                    'roles' => ['managePost'],
                ],
                [
                    'allow' => true,
                    'actions' => ['view'],
                    'roles' => ['viewPost'],
                ],
                [
                    'allow' => true,
                    'actions' => ['create'],
                    'roles' => ['createPost'],
                ],
                [
                    'allow' => true,
                    'actions' => ['update'],
                    'roles' => ['updatePost'],
                ],
                [
                    'allow' => true,
                    'actions' => ['delete'],
                    'roles' => ['deletePost'],
                ],
            ],
        ],
    ];
}

Если все операции CRUD управляются совместно, рекомендуется использовать одно разрешение, например managePost, и проверить его в yii\web\Controller::beforeAction().

Использование ролей по умолчанию

Роль по умолчанию - это роль, которая неявно назначается всем пользователям. Вызов yii\rbac\ManagerInterface::assign() не требуется, и данные авторизации не содержат информацию о назначении.

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

Роли по умолчанию часто используются в приложениях, у которых уже есть своего рода назначение ролей. Например, приложение может иметь столбец "groups" в своей таблице пользователей, чтобы представлять группу привилегий, к которой принадлежит каждый пользователь. Если каждая группа привилегий может быть сопоставлена ​​роли RBAC, вы можете использовать функцию роли по умолчанию, чтобы автоматически назначить каждого пользователя роли RBAC. Давайте использовать пример, чтобы показать, как это можно сделать.

Предположим, что в пользовательской таблице у вас есть столбец group, который использует 1 для представления группы администраторов и 2 - для группы авторов. Вы планируете иметь две роли RBAC admin и author для представления разрешений для этих двух групп, соответственно. Вы можете настроить данные RBAC следующим образом:

namespace app\rbac;

use Yii;
use yii\rbac\Rule;

/**
 * Checks if user group matches
 */
class UserGroupRule extends Rule
{
    public $name = 'userGroup';

    public function execute($user, $item, $params)
    {
        if (!Yii::$app->user->isGuest) {
            $group = Yii::$app->user->identity->group;
            if ($item->name === 'admin') {
                return $group == 1;
            } elseif ($item->name === 'author') {
                return $group == 1 || $group == 2;
            }
        }
        return false;
    }
}

$auth = Yii::$app->authManager;

$rule = new \app\rbac\UserGroupRule;
$auth->add($rule);

$author = $auth->createRole('author');
$author->ruleName = $rule->name;
$auth->add($author);
// ... add permissions as children of $author ...

$admin = $auth->createRole('admin');
$admin->ruleName = $rule->name;
$auth->add($admin);
$auth->addChild($admin, $author);
// ... add permissions as children of $admin ...

Обратите внимание, что в приведенном выше примере, поскольку «автор» добавляется как дочерний элемент «admin», когда вы реализуете метод execute() класса правил, вам также необходимо соблюдать эту иерархию. Вот почему, когда имя роли «автор», метод execute() вернет true, если группа пользователей равна 1 или 2 (что означает, что пользователь находится в группе «admin» или «author»).

Затем настройте authManager, указав две роли в yii\rbac\BaseManager::$defaultRoles:

return [
    // ...
    'components' => [
        'authManager' => [
            'class' => 'yii\rbac\PhpManager',
            'defaultRoles' => ['admin', 'author'],
        ],
        // ...
    ],
];

Теперь, если вы выполняете проверку доступа, обе роли admin и author будут проверены, оценив связанные с ними правила. Если правило возвращает true, это означает, что роль применима к текущему пользователю. На основе реализации вышеприведенного правила это означает, что если значение group пользователя равно 1, роль admin будет применяться к пользователю; И если значение group равно 2, будет применена роль author.