Как стать автором
Обновить

Symfony2: logging out

Время на прочтение 5 мин
Количество просмотров 13K
Автор оригинала: Joshua Thijssen
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. Со временем, уверен, будут и другие пути генерации пути для выхода.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Firewall
41.7% Файрвол 98
58.3% Файерволл 137
Проголосовали 235 пользователей. Воздержались 105 пользователей.
Теги:
Хабы:
+14
Комментарии 10
Комментарии Комментарии 10

Публикации

Истории

Работа

PHP программист
171 вакансия

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн