0 follower

Аутентификация и авторизация

Аутентификация и авторизация необходимы на страницах, доступных лишь некоторым пользователям. Аутентификация — проверка, является ли некто тем, за кого себя выдаёт. Обычно она подразумевает ввод логина и пароля, но также могут быть использованы и другие средства, такие как использование смарт-карты, отпечатков пальцев и др. Авторизация — проверка, может ли аутентифицированный пользователь выполнять определённые действия (их часто обозначают как ресурсы). Чаще всего это определяется проверкой, назначена ли пользователю определённая роль, имеющая доступ к ресурсам.

В Yii встроен удобный фреймворк аутентификации и авторизации (auth), который, в случае необходимости, может быть настроен под ваши задачи.

Центральным компонентом auth-фреймворка является предопределённый компонент приложения «пользователь» — объект, реализующий интерфейс IWebUser. Данный компонент содержит постоянную информацию о текущем пользователе. Мы можем получить к ней доступ из любого места приложения, используя Yii::app()->user.

Используя этот компонент, мы можем проверить, аутентифицирован ли пользователь, используя CWebUser::isGuest. Мы можем произвести вход или выход. Для проверки прав на определённые действия удобно воспользоваться CWebUser::checkAccess. Также есть возможность получить уникальный идентификатор и другие постоянные данные пользователя.

1. Определение класса Identity

Для того, чтобы аутентифицировать пользователя, мы определяем класс Identity, описывающий процедуру его опознавания. Данный класс должен реализовывать интерфейс IUserIdentity. Для разных моделей аутентификации (таких как OpenID или LDAP) могут быть определены несколько классов. Начать можно с расширения CUserIdentity, являющегося базовым классом для парольной аутентификации.

Главная задача при создании класса Identity — реализация метода IUserIdentity::authenticate. Также данный класс может содержать дополнительную информацию о пользователе, которая необходима нам в процессе работы с его сессией.

В следующем примере мы проверим, совпадают ли введённые имя пользователя и пароль с теми, что мы получим из базы данных при помощи Active Record. Также мы переопределим метод getId так, чтобы он возвращал переменную _id, заданную во время аутентификации (реализация по умолчанию возвращает ID объекта). В процессе аутентификации мы, используя CBaseUserIdentity::setState, сохраняем полученное значение title в переменной состояния с тем же именем.

class UserIdentity extends CUserIdentity
{
    private $_id;
    public function authenticate()
    {
        $record=User::model()->findByAttributes(array('username'=>$this->username));
        if($record===null)
            $this->errorCode=self::ERROR_USERNAME_INVALID;
        else if($record->password!==md5($this->password))
            $this->errorCode=self::ERROR_PASSWORD_INVALID;
        else
        {
            $this->_id=$record->id;
            $this->setState('title', $record->title);
            $this->errorCode=self::ERROR_NONE;
        }
        return !$this->errorCode;
    }
 
    public function getId()
    {
        return $this->_id;
    }
}

Информация, сохраняемая в переменные состояния, (через CBaseUserIdentity::setState) передаётся CWebUser, в котором она сохраняется в постоянном хранилище, таком как сессия. Данная информация становится доступной как свойства CWebUser. К примеру, мы можем получить значение title текущего пользователя при помощи Yii::app()->user->title (доступно с версии 1.0.3. В предыдущих версиях, использовался такой код: Yii::app()->user->getState('title'))

Инфо: По умолчанию CWebUser использует сессии для хранения данных. Если вы используете автоматический вход пользователя с помощью cookie (CWebUser::allowAutoLogin выставлен в true), данные пользователя будут также сохраняться в cookie. Убедитесь, что эти данные не содержат конфиденциальной информации, такой как пароли.

2. Вход и выход

Используя класс Identity и компонент User, мы можем с лёгкостью реализовать действия для входа и выхода:

// Аутентифицируем пользователя по имени и паролю
$identity=new UserIdentity($username,$password);
if($identity->authenticate())
    Yii::app()->user->login($identity);
else
    echo $identity->errorMessage;
......
// Выходим
Yii::app()->user->logout();

По умолчанию, после некоторого времени бездействия, зависящего от настроек сессии, будет произведён выход из системы. Для того, чтобы этого не происходило, необходимо выставить свойства компонента User allowAutoLogin в true и передать необходимое время жизни cookie в метод CWebUser::login. Пользователь будет автоматически аутентифицирован на сайте в течение указанного времени даже в том случае, если он закроет браузер. Данная возможность требует поддержки cookie в браузере пользователя.

// Автоматический вход в течение 7 дней.
// allowAutoLogin для компонента user должен быть выставлен в true.
Yii::app()->user->login($identity,3600*24*7);

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

Фильтр контроля доступа — схема авторизации, подразумевающая предварительную проверку прав текущего пользователя на вызываемое действие контроллера. Авторизация производится по имени пользователя, IP-адресу и типу запроса. Данный фильтр называется «accessControl».

Подсказка: Фильтр контроля доступа достаточен для реализации простых систем. Для более сложных вы можете использовать доступ на основе ролей (RBAC), который будет описан ниже.

Для управления доступом к действиям контроллера необходимо переопределить метод CController::filters (более подробно описано в разделе Фильтры).

class PostController extends CController
{
    ......
    public function filters()
    {
        return array(
            'accessControl',
        );
    }
}

Выше было описано, что фильтр access control применяется ко всем действиям контроллера PostController. Правила доступа, используемые фильтром определяются переопределением метода CController::accessRules контроллера.

class PostController extends CController
{
    ......
    public function accessRules()
    {
        return array(
            array('deny',
                'actions'=>array('create', 'edit'),
                'users'=>array('?'),
            ),
            array('allow',
                'actions'=>array('delete'),
                'roles'=>array('admin'),
            ),
            array('deny',
                'actions'=>array('delete'),
                'users'=>array('*'),
            ),
        );
    }
}

Приведённый код описывает три правила, каждое из которых представлено в виде массива. Первый элемент массива — 'allow'(разрешить) или 'deny'(запретить). Остальные — пары имя-значение, задающие параметры правила. В данных правилах задано, что: действия create и edit не могут быть выполнены анонимными пользователями, а действие delete может быть выполнено только пользователями с ролью admin.

Правила доступа разбираются поочерёдно в порядке их описания. Первое правило, совпадающее с текущими данными (например, с именем пользователя, ролью или IP) определяет результат авторизации. Если это разрешающее правило, действие может быть выполнено, если запрещающее — не может. Если ни одно из правил не совпало — действие может быть выполнено.

Подсказка: Чтобы быть уверенным, что действие не будет выполнено, необходимо запретить все действия, которые не разрешены, определив соответствующее правило в конце списка:

return array(
    // ... разные правила ...
    // это правило полностью запрещает действие 'delete'
    array('deny',
        'action'=>'delete',
    ),
);

Данное правило необходимо, так как если ни одно из правил не совпадёт, действие будет выполнено.

Правило доступа может включать параметры, по которым проверяется совпадение:

  • actions: позволяет указать действия в виде массива их идентификаторов. Сравнение регистронезависимо;

  • controllers: позволяет указать контроллеры в виде массива их идентификаторов. Сравнение регистронезависимо. Доступно начиная с версии 1.0.4;

  • users: позволяет указать пользователей. Для сравнения используется CWebUser::name. Сравнение регистронезависимо. В параметре могут быть использованы следующие специальные символы:

    • *: любой пользователь, включая анонимного.
    • ?: анонимный пользователь.
    • @: аутентифицированный пользователь.
  • roles: позволяет указать роли, используя доступ на основе ролей, описанный в следующем разделе. В частном случае, правило применится, если CWebUser::checkAccess вернёт true для одной из ролей. Роли стоит использовать в разрешающих правилах так как роль ассоциируется с возможностью выполнения какого-либо действия. Также стоит отметить, что, несмотря на то, что мы используем термин «роль», значением может быть любой элемент auth-фреймворка, такой как роли, задачи или операции;

  • ips: позволяет указать IP-адрес;

  • verbs: позволяет указать тип запросов (например, GET или POST). Сравнение регистронезависимо;

  • expression: позволяет указать выражение PHP, вычисление которого будет определять совпадение правила. Внутри выражения доступна переменная $user, указывающая на Yii::app()->user. Данный параметр доступен начиная с версии 1.0.3.

Обработка запроса авторизации

При неудачной авторизации, т.е. когда пользователю запрещено выполнять указанное действие, происходит следующее:

  • Если пользователь не аутентифицирован и в свойстве loginUrl компонета user задан URL страницы входа, браузер будет перенаправлен на эту страницу; Заметим, что по умолчанию loginUrl перенаправляет к странице site/login.

  • Иначе будет отображена ошибка HTTP с кодом 403.

При задании свойства loginUrl используется как относительный, так и абсолютный URL. Также можно передать массив, который будет использоваться CWebApplication::createUrl при формировании URL. Первый элемент массива задаёт маршрут до действия login вашего контроллера, а остальные пары имя-значение — GET-параметры. К примеру,

array(
    ......
    'components'=>array(
        'user'=>array(
            // это значение устанавливается по умолчанию
            'loginUrl'=>array('site/login'),
        ),
    ),
)

Если браузер был перенаправлен на страницу входа и вход удачный, вам может понадобиться перенаправить пользователя к той странице, на которой неудачно прошла авторизация. Как же узнать URL той страницы? Мы можем получить эту информацию из свойства returnUrl компонента user. Имея её, мы можем сделать перенаправление:

Yii::app()->request->redirect(Yii::app()->user->returnUrl);

4. Контроль доступа на основе ролей

Контроль доступа на основе ролей (RBAC) — простой, но мощный способ централизованного контроля доступа. Для сравнения данного метода с другими обратитесь к статье в Википедии.

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

Общие принципы

Основным понятием в RBAC Yii является элемент авторизации. Элемент авторизации — это права на выполнение какого-либо действия (создать новую запись в блоге, управление пользователями). В зависимости от структуры и цели, элементы авторизации могут быть разделены на операции, задачи и роли. Роль состоит из задач. Задача состоит из операций. Операция — разрешение на какое-либо действие (дальше не делится). К примеру, в системе может быть роль администратор, состоящая из задач управление записями и управление пользователями. Задача управление пользователями может состоять из операций создать пользователя, редактировать пользователя и удалить пользователя. Для достижения большей гибкости, роль в Yii может состоять из других ролей и операций. Задача может состоять из других задач. Операция — из других операций.

Элемент авторизации однозначно идентифицируется его уникальным именем.

Элемент авторизации может быть ассоциирован с бизнес-правилом — PHP-кодом, который будет использоваться при проверке доступа. Пользователь получит доступ к элементу только если код вернёт true. К примеру, при определении операции updatePost, будет не лишним добавить бизнес-правило, проверяющее соответствие ID пользователя ID автора записи. То есть, доступ к редактированию записи имеет только её автор.

Используя элементы авторизации мы можем построить иерархию авторизации. Элемент A — предок B в иерархии если A состоит из B (A наследует права, представленные в B). Элемент может иметь несколько потомков и несколько предков. Поэтому иерархия авторизации является скорее частично упорядоченным графом, чем деревом. В ней роли находятся на верхних уровнях, операции — на нижних. Посередине расположены задачи.

После построения иерархии авторизации мы можем назначать роли из неё пользователям нашего приложения. Пользователь получает все права роли, которая ему назначена. К примеру, если назначить пользователю роль администратор, он получит административные полномочия, такие как управление записями или управление пользователями (и соответствующие им операции, такие как создать пользователя).

А теперь самое приятное. В действии контроллера мы хотим проверить, может ли текущий пользователь удалить определённую запись. При использовании иерархии RBAC и назначенной пользователю роли, это делается очень просто:

if(Yii::app()->user->checkAccess('deletePost'))
{
    // удаляем запись
}

Настройка менеджера авторизации

Перед тем, как мы перейдём к построению иерархии авторизации и непосредственно проверке доступа, нам потребуется настроить компонент приложения authManager. В Yii есть два типа менеджеров авторизации: CPhpAuthManager и CDbAuthManager. Первый использует для хранения данных PHP, второй — базу данных. При настройке authManager необходимо указать, который из компонентов мы собираемся использовать и указать начальные значения свойств компонента. К примеру,

return array(
    'components'=>array(
        'db'=>array(
            'class'=>'CDbConnection',
            'connectionString'=>'sqlite:path/to/file.db',
        ),
        'authManager'=>array(
            'class'=>'CDbAuthManager',
            'connectionID'=>'db',
        ),
    ),
);

После этого мы можем обращаться к компоненту authManager используя Yii::app()->authManager.

Построение иерархии авторизации

Построение иерархии авторизации состоит их трёх этапов: задания элементов авторизации, описания связей между ними и назначение ролей пользователям. Компонент authManager предоставляет полный набор API для выполнения поставленных задач.

Для определения элемента авторизации следует воспользоваться одним из приведённых ниже методов:

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

После этого мы назначаем роли пользователям:

Приведём пример построения иерархии авторизации с использованием данного API:

$auth=Yii::app()->authManager;
 
$auth->createOperation('createPost','создание записи');
$auth->createOperation('readPost','просмотр записи');
$auth->createOperation('updatePost','редактирование записи');
$auth->createOperation('deletePost','удаление записи');
 
$bizRule='return Yii::app()->user->id==$params["post"]->authID;';
$task=$auth->createTask('updateOwnPost','редактирование своей записи',$bizRule);
$task->addChild('updatePost');
 
$role=$auth->createRole('reader');
$role->addChild('readPost');
 
$role=$auth->createRole('author');
$role->addChild('reader');
$role->addChild('createPost');
$role->addChild('updateOwnPost');
 
$role=$auth->createRole('editor');
$role->addChild('reader');
$role->addChild('updatePost');
 
$role=$auth->createRole('admin');
$role->addChild('editor');
$role->addChild('author');
$role->addChild('deletePost');
 
$auth->assign('reader','readerA');
$auth->assign('author','authorB');
$auth->assign('editor','editorC');
$auth->assign('admin','adminD');

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

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

При построении иерархии авторизации мы можем назначить роль, задачу или операцию бизнес-правилу. Также мы можем указать его при назначении роли пользователю. Бизнес-правило — PHP-код, использующийся при проверке доступа. Возвращаемое данным кодом значение определяет, применять ли данную роль к текущему пользователю. В примере выше мы применили бизнес-правило для описания задачи updateOwnPost. В нём мы проверяем, совпадает ли ID текущего пользователя с ID автора записи. Информация о записи в массиве $params передаётся разработчиком при проверке доступа.

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

Для проверки доступа нам необходимо знать имя элемента авторизации. К примеру, чтобы проверить, может ли текущий пользователь создать запись, необходимо узнать, имеет ли он права, описанные операцией createPost. После этого мы можем вызвать CWebUser::checkAccess:

if(Yii::app()->user->checkAccess('createPost'))
{
    // создаём запись
}

Если правило авторизации использует бизнес-правило, требующее дополнительных параметров, необходимо их передать. К примеру, чтобы проверить, может ли пользователь редактировать запись, нужно сделать следующее:

$params=array('post'=>$post);
if(Yii::app()->user->checkAccess('updateOwnPost',$params))
{
    // обновляем запись
}

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

Примечание: данная возможность доступна начиная с версии 1.0.3

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

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

Роли по умолчанию описываются в свойстве CAuthManager::defaultRoles. К примеру, приведённая ниже конфигурация описывает две роли по умолчанию: authenticated и guest.

return array(
    'components'=>array(
        'authManager'=>array(
            'class'=>'CDbAuthManager',
            'defaultRoles'=>array('authenticated', 'guest'),
        ),
    ),
);

Так как роль по умолчанию назначается каждому пользователю, обычно требуется использовать бизнес-правило, определяющее, к каким именно пользователям её применять. К примеру, следующий код определяет две роли: authenticated и guest, которые соответственно применяются к аутентифицированным пользователям и гостям.

$bizRule='return !Yii::app()->user->isGuest;';
$auth->createRole('authenticated', 'аутентифицированный пользователь', $bizRule);
 
$bizRule='return Yii::app()->user->isGuest;';
$auth->createRole('guest', 'гость', $bizRule);