Сразу оговорюсь, что официальная документация с примером WSSE-аутентификации не очень прояснила мне суть вопроса.
Необходимо сделать аутентификацию через метод API сервиса, который внешне похож на OpenID, но им не является. Механика работы аутентификации:
1. Проверка аутентификации пользователя и, если он не аутентифицирован то выдать ему сообщение с предложением пройти на страницу аутентификации. Тут срабатывает мой контроллер, отображающий выбор региона, а потом переадресовывающий пользователя на нужный метод API.
2. На странице он должен будет выбрать один параметр: регион. При клике на кнопку нужного ему региона пользователь попадает вызывает определённый Action контроллера (либо же это правильнее сделать внутри механизма аутентификации, но тогда как и куда редиректить пользователя?), где происходит запрос к API внешнего сервиса нужного региона (регион запоминается) для получения уникальной ссылки аутентификации, после чего пользователь редиректится по этой ссылке, где он должен будет подтвердить передачу сайту данных: токена доступа к API этого самого внешнего сервиса, юзернейма во внешнем сервисе и айдишника там же.
Тут непонятно, правильно ли будет делать это в контроллере, или надо переложить это тоже на кастомную аутентификацию? Если надо переложить, то как? После клика по кнопке региона от пользователя до возврата с данными на сайт больше ничего не потребуется.
3. После подтверждения пользователь редиректится обратно на сайт (в первом запросе внешнему API передаётся путь, куда редиректить пользователя), на страницу, которая принимает данные - токен, юзернейм, айдишник.
4. Система аутентификации в Listener'е создаёт токен, записывает в него пришедшие данные и отправляет токен на проверку:
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:
$user = $this->userProvider->loadUserByUsername($token->getUsername());
Если пользователь не существует - его нужно создать и задать ему юзернейм, и параметр айди на внешнем сервисе (не внутренний id, который primary key, а дополнительное поле, которое хранит внешний id).
if (!$user) {
$user = new User();
$user->setUsername($token->getUser());
//...
// Setting role
//...
}
В итоге у нас получается либо новый пользователь, либо найденный в базе. Новому токену задаётся юзер и статус аутентификации:
public function authenticate(TokenInterface $token)
{
// Тут вышеописанная рутина
$authenticatedToken->setUser($user);
$authenticatedToken->setAuthenticated(true);
return $authenticatedToken;
}
Здесь непонятно то, как можно в методе authenticate() провайдера аутентификации инициировать создание пользователя, ведь это не контроллер и тут просто так не получить менеджер сущностей и отправив нового пользователя на сохранение$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, у которого другой принцип работы. И
эту статью - тоже. Благодаря ним частично разобрался, но всё вышеописанное - пробелы после чтения.
Прошу прощения за такой объёмный "вопрос". Подозреваю, что может вскрыться что-то ещё, что я не понял, но если разберусь с вышеописанным механизмом - будет значительно проще разбираться дальше.