0 follower

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

Аутентифікація і авторизація необхідні на сторінках, доступних лише деяким користувачам. Аутентифікація - перевірка, чи є хтось тим, за кого себе видає. Зазвичай вона має на увазі введення логіна і пароля, але також можуть бути використані й інші засоби, такі як використання смарт-карти, відбитків пальців та ін. Авторизація - перевірка, чи може аутентифікований користувач виконувати певні дії (їх часто позначають як ресурси). Найчастіше це визначається перевіркою, чи призначена користувачеві певна роль, яка має доступ до ресурсів.

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

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

Використовуючи цей компонент, ми можемо перевірити, чи аутентифікований користувач, використовуючи CWebUser::isGuest. Ми можемо зробити вхід або вихід. Для перевірки прав на певні дії зручно скористатися CWebUser::checkAccess. Також є можливість отримати унікальний ідентифікатор та інші постійні дані користувача.

1. Визначення класу 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. Переконайтеся, що ці дані не містять конфіденційної інформації, такої як паролі.

Зберігання паролів у базі даних

Безпечне зберігання паролів у базі даних вимагає деякої обережності. Зловмисник, що викрав вашу таблицю користувачів (або його резервну копію) може відновити паролі, використовуючи стандартні підходи, у випадку, якщо ви не передбачите захист від них. Зокрема, ви повинні додавати "сіль" до паролю перед хешуванням і використовувати хеш-функцію, яка забере у нападника багато часу для обчислення. Наведений вище приклад коду використовує вбудований помічник Yii CPasswordHelper для хешування та валідації пароля (починаючи із версії 1.1.14). CPasswordHelper::hashPassword повертає достатньо стійкий хеш.

2. Вхід и вихід

Тепер, коли ми розібрали приклад реалізації класу 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.

3. Вхід на основі 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(). Викликається перед входом. Необхідно перевірити відповідність ключів у стані та базі даних.

4. Фільтр контролю доступа

Фільтр контролю доступа — схема авторизації, яка має на увазі попередню перевірку прав поточного користувача на дію контролера, що викликається. Авторизація виконується по імені користувача, 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.

5. Обробка запиту авторизації

При невдалій авторизації, тобто коли користувачу заборонено виконувати вказану дію, відбувається наступне:

  • Якщо користувач не аутентифікований і у властивості 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);

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

Контроль доступу на основі ролей (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'))
{
    // видаляємо запис
}

7. Налаштування менеджера авторизації

Перед тим, як ми перейдемо до побудови ієрархії авторизації і безпосередньо перевірці доступу, нам буде потрібно налаштувати компонент додатку 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.

8. Побудова ієрархії авторизації

Побудова ієрархії авторизації складається з трьох етапів: завдання елементів авторизації, описи звʼязків між ними і призначення ролей користувачам. компонент 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) завантажує їх автоматично. Тобто, наведений код запускається один раз, а НЕ для кожного запиту.

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

9. Використання бізнес-правил

При побудові ієрархії авторизації ми можемо призначити роль, завдання або операцію бізнес-правилу. Також ми можемо вказати його при призначенні ролі користувачу. Бізнес-правило - 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 та 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);

Інформація: Починаючи із версії 1.1.11 масив $params, який передається до бізнес правила, має ключ userId, значення цього ключа це id користувача, для якого ми перевіряємо бізнес правило. Вам було б це потрібно, якщо б ви викликали CDbAuthManager::checkAccess() або CPhpAuthManager::checkAccess() у місцях, де Yii::app()->user відсутній або перевіряєте доступ для іншого користувача.

Found a typo or you think this page needs improvement?
Edit it on github !