Я создал отдельный сервис SSO аутентификации 🔐
чтобы собрать воедино всю аутентификацию и авторизацию многочисленных микросервисов проекта в одном месте. Однако, перенести всю локальную авторизацию в SSO не так-то просто и требует глубокого осмысления пути интеграции. Поэтому пока я морально к этому не готов и отложил это занятие на потом 😌
В то же время обнаружилось, что авторизация Вконтакте перестала работать ❌ оказалось, что тот способ с неограниченными access_token’ми вконтакт признал устаревшим и теперь он перестал работать. Возникла острая необходимость срочно переписывать подход на новые рельсы 🚨
Здесь как-раз очень удачно появился SSO, т.к. там аутентификация через ВК написана с новым подходом ✅
Однако, SSO позволяет авторизовываться не только через ВК, но и через Email, Яндекс, Google и Github. И теперь мне нужно как-то чтобы заставить пользователя авторизоваться именно через Вконтакте 🤯
Долго думал над вариантами, и каждый из них в рамках действующих потоков авторизации выглядел не очень удобно или даже странно.
В итоге решил реализовать обособленный поток аутентификации, который и будет выполнять мою задачу 🧵
Задачи обособленного потока:
🔹 аутентифицироваться только через выбранного провайдера (в данном случае ВК), без выбора других вариантов
🔹 текущая сессия пользователя в SSO не должна быть нарушена, даже если по обособленной аутентификации вошел другой пользователь
🔹 реализация общения с ВК (получение code, обмен на access_token) должна быть спринговая, т.е. нужно как-то задействовать текущий flow, но вовремя его оборвать, не затереть текущую сессию
🔹 если текущая сессия пользователя в SSO имеет привязку к аккаунту ВК, показывать пользователю страницу с вариантами выбора: либо войти как текущий пользователь, либо войти как другой пользователь
Взаимодействие должно выглядеть таким образом:
🔗 Отправляем пользователя ссылку инициализации
1 | /api/separate-auth/init?provider=vkontakte&client_id=test-client&redirect_uri=http://localhost:8080/separate-auth-result |
🎯 Ожидаем результат по адресу
1 | http://localhost:8080/separate-auth-result?userId=SIME_USER_UUID |
Итак, искал множество подходов для реализации, пробовал варианты, мучал нейросеть и ковырялся в документации. В итоге пришел к такому решению:
1️⃣ При заходе на ссылку инициализации, смотрим есть ли текущая сессия в SSO с привязанным аккаунтом ВК, и отправляем пользователю 302 редирект либо на страницу выбора пользователя, либо прямиком в авторизацию Вконтакта
2️⃣ Также немного модифицирую ссылку авторизации Вконтакта, добавляем query параметр, в котором указываем, что данная авторизация должна быть в обособленном режиме (пример: https://mysso.site/oauth2/authorization/vkontakte?separate=SEPARATE_UUID)
3️⃣ Написал свою реализацию OAuth2AuthorizationRequestResolver, в которой как-раз вытягиваю этот SEPARATE_UUID и сохраняю в authorizationRequest.additionalParameters. Теперь в рамках всего flow будем его знать
4️⃣ Далее работает уже написанная логика авторизации ВК на спринговых OAUTH2 классах. В конце авторизации генерируется эвент успеха. Он мне и нужен, т.к. это происходит до записи новой авторизации в securityContextHolder
5️⃣ Пишу @EventListener на AuthenticationSuccessEvent, извлекаю SEPARATE_UUID, access_token Вконтакта, идентификатор пользователя в SSO и внаглую кидаю эксепшн кастомного класса 💥
6️⃣ Всё раскручивается обратно и авторизация падает, но есть одна хитрость — я зарегистрировал кастомный OncePerRequestFilter и воткнул его прям перед OAuth2LoginAuthenticationFilter. Этот фильтр просто перехватывает моё кастомное исключение, останавливает дальнейшее раскручивание stack-trace, и делает 302 response.sendRedirect пользователю 🎯
Красиво? Мне кажется весьма неплохо 😎
Все задачи обособленного потока выполнены, и максимально использована спринговая реализация потоков ✅
Скоро интегрирую решение в CRM и можно будет настраивать автоматическую выгрузку ассортимента на витрину Вконтакте💪
