Symfony2: logging out

imageОдне із золотих правил Symfony2 — ніколи не хардкодить всередині коду або шаблонів які-небудь посилання і шляхи. Дотримання цього правила і генерація посилань через роутер значно полегшать ваше життя. Однак є одна річ, яку я часто спостерігаю: люди продовжують хардкодить посилання на вихід із системи, наприклад, як "/logout", тільки от сам процес логаута трохи складніше, ніж може здаватися і використання такої посилання може працювати в більшості випадків, але це не буде кращим рішенням проблеми.

Трохи інформації про компоненті (і бандл) Symfony2 Security

Більшість розробників знають, що можна зробити кілька захищених розділів всередині одного проекту. Наприклад, це може бути панель для звичайних користувачів (зареєстровані користувачі) шляхом /secure. І, можливо, у вас в проекті може бути окрема панель адміністрування за адресою /admin і є окрема зона для користувачів API, яка знаходиться в розділі з адресою /api. Також, можна зробити «захищену зону», якої взагалі не потрібен захист — такий підхід використовується в тулбарі Symfony2 для розробників. Зрештою, можна взагалі все це перенести в одну велику зону, в якій буде реалізовано декілька варіантів визначення хто має доступ до захищеної частини проекту. Взагалі, хоч поділ проекту на окремі зони і робить ваш проект складніше, це дає деякі переваги.

Кожна із захищених зон викликає свій файрвол, який і визначає, автентифікувати користувача чи ні. Кожен файрвол відділений від інших: якщо ви авторизувались в одному з них, це не означає, що ви автоматично аутентифицированы в інших є лише один активний файрвол (той самий, який співпав з шаблоном URL). Це має значення, так як різні файрволы можуть використовувати різні бази даних або просто використовувати різні способи аутентифікації (наприклад, API можна використовувати OAuth Token, в той час як інші розділи можуть використовувати форму для входу).

Це також означає, що кожен файрволл має різні шляхи логаута, а для деяких з них логаут як такої не існує. Приклад security.yml

# Розділ розробника
dev:
pattern: ^/(_(profiler|wdt)|css|images|js/
security: false

# Розділ адміна
superadminstuff:
pattern: ^/admin
http_basic:
provider: memory_user_provider
realm: "Super Admin section!"

# все інше зі звичайною формою для входу
main:
pattern: ^/
form_login:
provider: fos_userbundle
csrf_provider: form.csrf_provider
login_path: /login
logout: true

Це блок «firewall» в security.yml і в ньому визначено 3 файрвола — dev, superadminstuff та main. dev взагалі не використовує аутентифікацію (security=false), що означає, що доступ дозволений всім і шляхи "/js", "/css" та інші не управляються файрволом main.

Наступний набір правил захищає зону адміністрування. У ній використовується http_basic в якості входу, тобто браузер покаже діалогове вікно, в якому попросить вас вказати логін і пароль (насправді це не дуже безпечно, так як вони будуть передаватися як plain text). Більше того, браузер буде відправляти логін і пароль при кожному запиті до проекту. Symfony2 може перевірити ці дані використовуючи провайдер «memory_user_provider», блок якого я не навів, але в ньому, зазвичай, вказується кілька стандартних користувачів та логін/пароль (прямо у файлі налаштувань, а не у базі даних).

В http-basic насправді немає логаута тому, що єдиний спосіб виходу в такому випадку — припинити надсилати запити. Очищення кеша або перезапуск браузера зазвичай допомагає зробити логаут в такому випадку.

Останній файрвол — main. Замість http basic він використовує форму для входу. Тут використовується FOSUserBundle, в якому є своя форма входу і методи для її обробки, тому єдине, що потрібно від розробника — трохи кастомизировать їх, а не писати свої.

У разі, якщо ви відкриваєте сторінку в цьому файрвол, і не увійшли раніше — Symfony2 автоматично перенаправляє користувача на сторінку входу, котра вказана в параметрі login_path в блоці form_login. Зазвичай (за замовчуванням), це шлях з адресою /login. Як тільки користувач увійшов, Symfony2 збереже користувача та роль всередині її сесії і при наступному запиті користувачеві не доведеться входити знову.

Вихід з такого файрвола досить простий — потрібно перейти на його сторінку виходу. Але що це за сторінку?

У прикладі вище, використовується параметр «logout: " true». Варто звернути увагу, що цей параметр знаходиться в блоці файрвола, а не в блоці form_login. Вказуючи logout: true, ми говоримо Symfony2 використовувати стандартні налаштування логаута, а саме:

logout:
csrf_parameter: _csrf_token
csrf_token_generator: ~
csrf_token_id: logout
path: /logout
target: /
success_handler: ~
invalidate_session: true
delete_cookies:
name:
path: null
domain: null
handlers: []

Як можна помітити, вказується шлях, по якому буде відбуватися вихід. Але є одна особливість: за замовчуванням, логаут листенер запускається перед викликом якогось контролера або екшену, а потім робить редирект на сторінку, вказану в параметрі «target». Якщо у вас свій обробник logout-події, яка зазначається в пункті «handlers», і він НЕ повертає об'єкт HTTP Response, то викликається поточний рауса. Тобто за замовчуванням, ваші контролер/екшн не будуть викликані, АЛЕ вони повинні бути вказані (тобто, роутер Symfony2 зобов'язати знати про нього). З цієї причини можна знайти дивний екшн logout FOSUserBundle, кидає виняток, так як він ніколи не буде викликаний.

Логаут

Отже, що ж робити з виходом? В першу чергу, не варто хардкодить URL. Навіть якщо ви використовуєте маршрут замість url, ви можете поміняти його всередині конфігурації і вихід перестане працювати. Що дійсно варто робити — вказувати через twig посилання або маршрут, зазначений у конфігурації. На щастя, SecurityBundle має розширення для twig, який допоможе це зробити. Мова йде про функції logout_url і logout_path. Ці функції отримують на вхід id файрвола (наприклад, «main», «dev» і т.д.) і генерує правильну адресу виходу для нього:

<a href="{{ logout_path('main') }}">Logout</a>

В цьому випадку відбудеться вибірка правильної адреси і в якості бонусу додасться csrf-token, якщо це було вказано в конфігурації. Таким чином, замість того, щоб вказати в шаблоні адресу сторінки, потрібно вказувати той файрвол, який використовується в даний момент.

Правда, тепер ваші шаблони знають більше ніж треба і необхідно вказати ім'я файрвола вручну. У більшості випадків це нормально, але іноді це може викликати проблеми (наприклад, якщо ви використовуєте меню, де використовується twig). Щоб уникнути проблем з цим існує можливість отримати ім'я поточного файрвола, нехай і трохи неправильна:

<a href="{{ logout_path(app.security.token.providerKey) }}">Logout</a>

Всередині токена контексту безпеки знаходиться необхідне нам назву поточного файрвола. «Неправильність» рішення в тому, що глобальна змінна app.security Symfony версії 2.6 буде в статусі deprecated і вилучена у версії 3.0. З часом, впевнений, будуть і інші шляхи генерації шляхи для виходу.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.