Symfony Вопросы по созданию своего механизма аутентификации в Symfony2

  • Автор темы Автор темы skobkin
  • Дата начала Дата начала

skobkin

Новичок
Сразу оговорюсь, что официальная документация с примером WSSE-аутентификации не очень прояснила мне суть вопроса.

Необходимо сделать аутентификацию через метод API сервиса, который внешне похож на OpenID, но им не является. Механика работы аутентификации:
1. Проверка аутентификации пользователя и, если он не аутентифицирован то выдать ему сообщение с предложением пройти на страницу аутентификации. С этим, по идее, справляется файрвол, но мне нужно выдавать определённый ответ, а не стандартную страницу, т.к. я предоставляю API на JSON. Тут непонятно, где правильно будет задать форму ответа клиенту.
2. На странице он должен будет выбрать один параметр: регион. При клике на кнопку нужного ему региона пользователь попадает вызывает определённый Action контроллера (либо же это правильнее сделать внутри механизма аутентификации, но тогда как и куда редиректить пользователя?), где происходит запрос к API внешнего сервиса нужного региона (регион запоминается) для получения уникальной ссылки аутентификации, после чего пользователь редиректится по этой ссылке, где он должен будет подтвердить передачу сайту данных: токена доступа к API этого самого внешнего сервиса, юзернейма во внешнем сервисе и айдишника там же. Тут непонятно, правильно ли будет делать это в контроллере, или надо переложить это тоже на кастомную аутентификацию? Если надо переложить, то как? После клика по кнопке региона от пользователя до возврата с данными на сайт больше ничего не потребуется.
3. После подтверждения пользователь редиректится обратно на сайт (в первом запросе внешнему API передаётся путь, куда редиректить пользователя), на страницу, которая принимает данные - токен, юзернейм, айдишник.
4. Система аутентификации в Listener'е создаёт токен, записывает в него пришедшие данные и отправляет токен на проверку:
PHP:
protected function attemptAuthentication(Request $request)
    {
        if ($request->get($this->options['status_parameter'], null, true) == 'ok') {
            // Наполнение токена данными
            return $this->authenticationManager->authenticate($token);
        }
    }
Тут, к счастью, всё понятно. Разве что, хотелось бы уточнить, когда вызывается метод attemptAtuthentication() у Listener'а. Если только при переходе на адрес, который указан в файрволе как адрес проверки данный - значит я всё правильно понял.
5. В дело вступает кастомный провайдер аутентификации. В методе authenticate() происходит вызов другого метода, который делает запрос ко внешнему сервису для проверки валидности токена (ведь с редиректом можно вернуть фальшивый юзернейм) и, если токен валидный - происходит поиск пользователя с таким юзернеймом, который указан в токене через userProvider:
PHP:
$user = $this->userProvider->loadUserByUsername($token->getUsername());
Если пользователь не существует - его нужно создать и задать ему юзернейм, и параметр айди на внешнем сервисе (не внутренний id, который primary key, а дополнительное поле, которое хранит внешний id).
PHP:
if (!$user) {
    $user = new User();
    $user->setUsername($token->getUser());
    //...
    // Setting role
    //...
}
В итоге у нас получается либо новый пользователь, либо найденный в базе. Новому токену задаётся юзер и статус аутентификации:
PHP:
public function authenticate(TokenInterface $token)
{
    // Тут вышеописанная рутина
    $authenticatedToken->setUser($user);
    $authenticatedToken->setAuthenticated(true);
    return $authenticatedToken;
}
Здесь непонятно то, как можно в методе authenticate() провайдера аутентификации инициировать создание пользователя, ведь это не контроллер и тут просто так не получить менеджер сущностей и отправив нового пользователя на сохранение
PHP:
$em = $this->getDoctrine()->getManager();
$em->persist($user);
$em->flush();
Наверное, мне нужно каким-то образом передать в конструктор провайдера сервис Doctrine?
6. После этого, по идее, где-то нужно положить текущий токен в контекст безопасности, да и пользователя где-то нужно положить так, чтобы в дальнейшем он был прозрачно доступен по $this->getUser() в контроллерах. Тут непонятно, где конкретно это сделать будет правильно. Или система аутентификации позаботится об этом сама?
7. Ну и как-то после этого процесса нужно опять же выдать свой кастомный ответ в JSON о том, что аутентификация пройдена.

P.S. Сейчас имеется токен, который, кажется, реализован правильно. По крайней мере, с ним всё просто и понятно. Ещё имеется Listener унаследованный от AbstractAuthenticationListener. У абстрактного класса в параметрах есть 'login_path'. Я так понимаю, где-то там мне и нужно отображать выбор региона для пользователя, но так как это обрабатывает бандл Security, а не мои контроллеры - я пока не понимаю, как это реализовать. 'check_path' - это, как я понимаю, страница, куда пользователь должен приземлиться после редиректа со стороннего сервиса (в случае с обычной формой логина сюда должны прилетать данные формы, так?).
P.P.S. Официальную документацию читал. Там речь о WSSE, у которого другой принцип работы. И эту статью - тоже. Благодаря ним частично разобрался, но всё вышеописанное - пробелы после чтения.

Прошу прощения за такой объёмный "вопрос". Подозреваю, что может вскрыться что-то ещё, что я не понял, но если разберусь с вышеописанным механизмом - будет значительно проще разбираться дальше.
 
Последнее редактирование:
Тут непонятно, где правильно будет задать форму ответа клиенту.
Так ты api пишешь, или авторизацию на сайте? Что есть форма ответа клиенту?
Тут непонятно, правильно ли будет делать это в контроллере, или надо переложить это тоже на кастомную аутентификацию? Если надо переложить, то как? После клика по кнопке региона от пользователя до возврата с данными на сайт больше ничего не потребуется.
Сделай сначала в контроллере. Когда убедишься, что все работает, зарефактори в своих классах аутентификации.
Наверное, мне нужно каким-то образом передать в конструктор провайдера сервис Doctrine?
Да, так и нужно. ЮзерПровайдер надо описать в контейнере.
Или система аутентификации позаботится об этом сама?
На сколько я помню, да. Она достанет пользователя согласно текущему, прошедшему проверку токену.
 
Так ты api пишешь, или авторизацию на сайте? Что есть форма ответа клиенту?
Аутентификация происходит в обычном окне, где, по сути, браузер. А потом общение через API.
Сделай сначала в контроллере. Когда убедишься, что все работает, зарефактори в своих классах аутентификации.
В контроллере-то уже сделал. Но в Symfony2 так делать - это костыли. Собственно, проблемы-то именно с реализацией схемы в провайдере аутентификации и сопутствующих классах.

В общем, к сожалению, из-за временных ограничений пока что придётся оставить решение с контроллером, который сам пропихивает токен в security context и реализует кривоватое remember me cookie, но в планах как только появится время сесть и всё это отрефакторить. А то костыли тянут за собой другие костыли, а у меня на них аллергия. Так что, если не разберусь со второго захода - снова вернусь и уточню вопросы.
Спасибо.
 
Мне кажется, или можно обойтись настройкой hwioauthbundle?
 
Сверху