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

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, у которого другой принцип работы. И эту статью - тоже. Благодаря ним частично разобрался, но всё вышеописанное - пробелы после чтения.

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

keltanas

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

skobkin

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

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

hell0w0rd

Продвинутый новичок
Мне кажется, или можно обойтись настройкой hwioauthbundle?
 
Сверху