Оглавление

Начало Основы Работа с формами Работа с БД Кэширование Расширение Yii Тестирование Специальные темы
Зачем реклама?

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

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

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

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

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

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

Как было упомянуто ранее, аутентификация — это процесс проверки личности пользователя. Типичное веб-приложение для такой проверки обычно использует логин и пароль. Тем не менее, может потребоваться реализовать проверку другими методами. Чтобы добавить поддержку различных методов аутентификации, в Yii имеется соответствующий identity класс.

Мы реализуем класс identity, который содержит нужную нам логику аутентификации. Такой класс должен реализовать интерфейс IUserIdentity. Для различных подходов к аутентификации могут быть реализованы различные классы (например, OpenID, LDAP, Twitter OAuth или Facebook Connect). При создании своей реализации необходимо расширить класс CUserIdentity, являющийся базовым классом, который реализует проверку по логину и паролю.

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

Пример #

В приведённом ниже примере мы используем класс identity и покажем, как реализовать аутентификацию по базе данных. Данный подход типичен почти для всех приложений. Пользователь будет вводить логин и пароль в форму. Введённые данные будем проверять с использованием модели ActiveRecord, соответствующей таблице пользователей в БД. В данном примере показано следующее:

  1. Реализация метода authenticate() для проверки данных по БД.
  2. Перекрытие метода CUserIdentity::getId() для возврата _id. По умолчанию в качестве ID возвращается имя пользователя.
  3. Использование метода setState() (CBaseUserIdentity::setState) для хранения информации, необходимой при каждом запросе.
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(!
CPasswordHelper::verifyPassword($this->password,$record->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;
    }
}

В следующем подразделе мы рассмотрим реализацию входа и выхода, используя наш identity класс в методе login пользователя. Вся информация, которую мы храним в состояниях (путём вызова CBaseUserIdentity::setState) будет передана в CWebUser, который, в свою очередь, будет хранить её в постоянном хранилище, таком как сессии. К данной информации можно будет обращаться как к свойствам CWebUser. В нашем примере мы сохранили имя пользователя, используя $this->setState('title', $record->title);. Как только пользователь успешно войдёт в приложение, мы сможем получить его title используя Yii::app()->user->title.

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

Хранение паролей в базе данных #

Безопасное хранение паролей в базе данных требует определённой аккуратности. Атакующий, получивший досутп к базе или резеврным копиям может восстановить пароли используя достаточно распространённые приёмы, если от них не защититься. Пример кода выше использует встроенный класс CPasswordHelper, доступный с версии 1.1.14, для хеширования и проверки пароля. CPasswordHelper::hashPassword возвращает стойкий ко взлому хеш.

Вход и выход #

Теперь, когда мы разобрали пример реализации класса identity, мы можем использовать его для реализации входа и выхода:

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

// Выходим
Yii::app()->user->logout();

Мы создаём новый объект UserIdentity и передаём в его конструктор параметры аутентификации (то есть $username и $password, введённые пользователем). Далее просто вызываем метод authenticate(). В случае успешной проверки данных мы передаём объект в метод CWebUser::login, который сохраняет информацию в постоянном хранилище (по умолчанию в сессиях PHP) и делает её доступной в последующих запросах. Если аутентификация не проходит, мы можем получить информацию об ошибке из свойства errorMessage.

Проверить, является ли пользователь аутентифицированным, очень просто. Для этого можно воспользоваться Yii::app()->user->isGuest. При использовании постоянного хранилища, такого как сессии (по умолчанию) и/или cookie (описано ниже), для хранения информации о пользователе, пользователь может оставаться аутентифицированным в последующих запросах. В этом случае нет необходимости использовать класс UserIdentity и показывать форму входа. CWebUser автоматически загрузит необходимую информацию из постоянного хранилища и использует её при обращении к Yii::app()->user->isGuest.

Вход на основе cookie #

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

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

Как уже упоминалось выше, когда включен вход на основе cookie, состояния, сохраняемые при помощи CBaseUserIdentity::setState, также будут сохраняться в cookie. При следующем входе состояния считываются из cookie и становятся доступными через Yii::app()->user.

Несмотря на то, что в Yii имеются средства для предотвращения подмены состояний в cookie на стороне клиента, не рекомендуется хранить в состояниях важную информацию. Гораздо более правильным решением будет хранение её в постоянном хранилище на стороне сервера (например, в БД).

Кроме того, для серьёзных приложений рекомендуется улучшить стратегию входа по cookie следующим образом:

  • При успешном входе после заполнения формы генерируем и храним случайный ключ как в cookie состояния, так и в постоянном хранилище на сервере (т.е. в БД).

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

  • Если пользователь входит через форму ещё раз, ключ регенерируется.

Данная стратегия исключает возможность повторного использования старого состояния cookie, в котором может находится устаревшая информация.

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

  • CUserIdentity::authenticate(). Здесь производится аутентификация. Если пользователь аутентифицирован, необходимо сгенерировать новый ключ и сохранить его в cookie состояния (при помощи CBaseUserIdentity::setState) и в постояное хранилище на стороне сервера (например, в БД).

  • CWebUser::beforeLogin(). Вызывается перед входом. Необходимо проверить соответствие ключей в состоянии и базе данных.

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

Фильтр контроля доступа — схема авторизации, подразумевающая предварительную проверку прав текущего пользователя на вызываемое действие контроллера. Авторизация производится по имени пользователя, 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',
        
'actions'=>array('delete'),
    ),
);

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

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

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

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

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

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

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

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

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

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

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

  • Если пользователь не аутентифицирован и в свойстве 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);

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

Контроль доступа на основе ролей (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');

После создания элементов авторизации, компонент authManager (или его наследники, например, CPhpAuthManager, CDbAuthManager) загружает их автоматически. То есть, приведённый код запускается один раз, а НЕ для каждого запроса.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

$bizRule='return Yii::app()->user->name === "admin";';
$auth->createRole('admin''администратор'$bizRule);

$bizRule='return Yii::app()->user->isGuest;';
$auth->createRole('guest''гость'$bizRule);

Информация: Начиная с версии 1.1.11 массив $params, передаваемый в бизнес-правило, всегда содержит ключ userId с id пользователя, для которого проверяется правило. Это особенно удобно при использовании CDbAuthManager::checkAccess() или CPhpAuthManager::checkAccess() когда Yii::app()->user не является пользователем, которого вы проверяете.


Зачем реклама?