О построении флоу авторизации разработчики задумываются чуть ли не в каждом проекте. Единого правильного решения здесь нет и быть не может.
Хороший авторизационный флоу опирается на особенности бизнес-логики и архитектуры конкретного продукта, цели клиента и технические возможности команды разработки.
Именно об этом я подробнее расскажу в этой статье. Приведенные советы основываются на моем опыте.
О чем мы поговорим?
Сначала кратко объясню суть проекта. Он объединяет три продукта единого фармацевтического сервиса. Одно приложение может отслеживать товары в аптеках, другое — мониторить состояние пациентов. Пользователи сервиса должны свободно пользоваться двумя продуктами без повторной авторизации.
Настроить такую возможность и было нашей задачей. Вот только пермиссии на каждом домене для каждого пользователя разные. Где-то пользователь может создавать новые страницы, где только просматривать имеющиеся. Это явилось для нас главной сложностью при реализации авторизационного флоу. Прежде чем объяснить, как мы воплощали принцип Single Sign On, кратко напомню, как он работает.
Single Sign On — это технология, позволяющая авторизироваться один раз для использования нескольких продуктов.
К примеру, есть решение Microsoft. Вы передаете креды для входа в аккаунт только раз, а затем открываете любые связанные ресурсы: OneDrive, Office и т.д. Это упрощает жизнь пользователя и экономит его время.
Что необходимо для реализации SSO? Берем браузер-клиент, два домена и единственный авторизационный сервер, где хранятся пользователи обоих доменов:
Далее он обращается ко второму домену. Юзер там не авторизован, поэтому система перенаправляет его на тот же единственный авторизационный сервер. Он снова проверяет cookies, видит старую запись об успешной авторизации и открывает доступ и ко второму домену. Опять же без введения кредов.
В нашем проекте таких связанных доменов было три, а в роли авторизационного сервера выступал KeyCloak. Пользователю фармацевтического сервиса достаточно один раз указать логин и пароль, чтобы получить доступ к любому из продуктов и легко переходить между ними:
Но с авторизационным флоу возникли сложности сразу в нескольких моментах:
Учитывая эти проблемы и технические возможности, наша команда начала искать решения в рамках SSO. О каждом из путей расскажу по отдельности.
Этот вариант показался очевидным. Что нас натолкнуло на такой выбор, так это использование в проекте репликации, когда один продукт копирует данные с другого. Тогда флоу выглядел бы следующим образом:
На этом этапе понадобится Authorization Service — хранилище пермиссий. Все три сервиса, с которыми общается клиент, будут использовать данные Authorization Service и реплицировать пермиссии. Они могут храниться как в базе данных, так и в кэше.
При поступлении запроса на чтение, добавление или другое действие пользователя система будет обращаться к реплицированным пермиссиям и будет проверять, есть ли они у этого пользователя. Так процесс происходит во всех продуктах:
Помимо решения вопроса пермиссий, есть и другие преимущества флоу с репликацией. Например, вся передаваемая информация будет актуальной. Это становится возможным за счет реплицирования данных.
Но и у этого подхода есть недостатки:
Так что мы снова начали размышлять над оптимальным решением…
Следующий вариант — размещение информации о правах пользователя в Bearer Token. При авторизации пользователя KeyCloak может получить пермиссии из хранилища Authorization Service.
Сценарий получился бы следующим: пользователь вводит логин и пароль, генерируется Bearer-токен, и в него записываются предназначенные авторизованному юзеру пермиссии.
Когда клиент пошлет запрос на тот или иной продукт, сервис уже на своей стороне сам валидирует его:
Вот два ощутимых плюса:
Но и в этом варианте была пара серьезных недостатков:
Итак, продолжаем поиски дальше…
Оптимальным для нас вариантом оказалось добавление в систему еще одного токена. В таком случае после ввода логина и пароля в KeyCloak клиент получает с авторизационным Bearer-токеном еще и JWT Auth Token.
Именно в нем и содержится информация о пользовательской пермиссии (в пределах одного продукта). Далее клиент с парой токенов отправляется к любому продукту, который сам и будет валидировать оба «ключа»: отдельно для авторизации и определения прав пользователя:
Издержки у этого флоу тоже были. Но ситуация оказалась некритической из-за определенных обстоятельств:
Наконец, мы построили авторизационный флоу на основе последнего сценария. Равномерно мы адаптировали этот вариант под особенности проекта и наши технические способности.
Как показано на схеме ниже, вся система состоит из нескольких основных элементов. Первый — это клиент или UI. Также есть авторизационный сервер KeyCloak. За ним следует Istio, представляющий Gateway Load Balancer. Для кэша предусмотрен Redis. Еще есть User Service, выполняющий роль упомянутого Authorization Service, хранилища для пермиссий. Последним здесь идет Service — это остальные сервисы, к которым присылают запросы на какую-то информацию.
Авторизационный флоу организован следующим образом:
Замечу, что в нашем проекте эта система имплементирована не до конца, поскольку пока нет процесса кэширования второго токена. Но мы добавим это в будущем и приведем процесс к идеалу.
Здесь мы имеем дело с многолетним legacy-проектом. В какой-то момент понадобилось перенести его на Azure. В этом случае центральная часть — это Image Service, хранилище изображений пользователей. Они могут загружать картинки и фотографии, просматривать и редактировать их. Клиентами выступают два элемента: веб-клиент и десктопное приложение.
В качестве примера я рассмотрю механизм авторизации при добавлении пользователем изображений.
Начальная авторизация в Image Service сделана просто. При попытке загрузить изображение в хранилище в теле запроса отправлялись три поля: специальный ключ и Поле 2 и Поле 3.
Первый ключ — единственная для всех пользователей и системы GUID-стринга. Она выполняла роль валидации по пользователю и его запросу. Поле 2 и Поле 3 помогали аутентификации и использовались для группирования картинок в папки:
Эта система имела много рисков:
Хотя в целом система работала и устраивала заказчика… Пока не возник вопрос миграции загрузок и хранения изображений на Azure. С основной частью функционала было понятно: мы частично переиспользовали логику из легаси-системы. Но авторизационный флоу стоило улучшить.
Для контекста коротко расскажу о самой миграции. При загрузке фотографий Azure общается с легаси-системой для сохранения определенных метаданных.
Поэтому был соблазн оставить все как есть, а валидировать потом. От этой идеи мы отказались, поскольку в планах заказчика уже были изменения авторизации по всем подпроектам.
Ключевым же фактором было сохранение клиентов во время миграции на Azure. Упомянутые веб-клиент и десктопное приложение не изменяли свои контракты, логику и процессы. Изменения могли затрагивать только Subscription Key и не более. Поэтому наша команда создала сразу два решения: промежуточное для «здесь и сейчас» и более дальновидное:
На сегодняшний день для авторизации на Azure мы используем API Management. Эта система может проксиировать запросы и объявлять сценарии выполнения. То есть, в какой последовательности добавлять данные или хедеры, как модифицировать запросы и т.п. Причем, первым шагом в API Management является получение JWT-токена. Это происходит за теми же полями, которые изначально были в легаси-проекте. Единственное исключение — Subscription Key. Мы избавились от него и добавили свой ключ, который может быть динамичным.
Валидирует его непосредственно API Management:
После получения JWT-токена система передает в ажуровскую Auth Function параметры авторизации пользователя. Речь идет об упомянутых выше Поле 2 и Поле 3. По этим значениям функции происходит извлечение JWT-токена из Redis, если раньше он был там размещен.
Если же токена в хранилище отсутствует, выполняется запрос к легаси-системе из Image Service по поводу валидности запроса по указанным параметрам. При подтверждении генерируется новый JWT-токен. Потом он размещается в Redis, а оттуда возвращается в API Management.
На этом моменте хочу сосредоточиться поподробнее. После получения токена и сохранения в хедере API Management валидирует ключ по своим параметрам. Почему это важно? Вот это и есть наше решение на будущее. С такой логикой мы подготовились к моменту, когда API Management будет всегда валидировать хедер и JWT-токен. Следующим шагом станет вызов функции добавления картинки в систему. Возможно, на нынешнем этапе развития проекта и его миграции это излишняя работа для разработчика. Но в будущем будет гораздо меньше переработок.
Чтобы лучше объяснить идею, покажу пример кода API Management. Система описывается простым XML-файлом:
Здесь есть первая проверка, где из реквеста из хедеров достается Subscription Key. Это именно то, что валидирует сам API Management. Он его сравнивает со своей переменной окружающих. В нашем случае это как раз сгенерированный самим API Management ключ. То есть он проверяет, есть ли такой ключ в запросе. Если есть, система переходит к следующему шагу — send-request:
Запрос отправляется в Auth Function для получения JWT-токена. Для этого создается запрос и передается тело с необходимыми параметрами. А дальше функция возвращает либо закэшированный, либо сгенерированный токен. Его природа не важна — важно возвращение функцией этого ключа:
Затем этот токен с добавлением Bearer сохраняется. Здесь доступен такой функционал как set-header к запросу. Благодаря этому мы можем сетить общепринятый хедер Authorization для Bearer-токена:
Появляется validate-jwt, позволяющий валидировать авторизационный токен, созданный на предыдущем этапе. В случае успешной валидации запрос посылается уже в Azure Function:
Обратите внимание: все это производится только для десктопного приложения. Веб-клиент этого не требует — там и без того безопасный канал общения. Далее остается выполнить запрос:
Решение может показаться достаточно сложным. Ведь нужно добавлять генерацию токена, валидацию и т.д. Но эти действия вполне оправданы. Когда в будущем заказчик сможет сам пользоваться определенным авторизационным сервером для получения Bearer-токена, достаточно будет удалить из схемы эти два шага (выделенные на скриншоте):
А дальше обновим API Management, и на этом модернизация мигрировавшей в Azure системы закончится. Все готово к изменениям в виде самостоятельной авторизации клиентом с помощью JWT-токена.
Приведенные примеры — это доказательство того, что авторизационный флоу может быть очень разным. Поэтому всегда тщательно исследуйте свой проект, старайтесь понять, какой способ авторизации будет наиболее оптимальным для конкретного случая.
В благословенные офисные времена, когда не было большой войны и коронавируса, люди гораздо больше общались…
Вот две истории из собственного опыта, с тех пор, когда только начинал делать свою карьеру…
«Ты же программист». За свою жизнь я много раз слышал эту фразу. От всех. Кто…
Отличные новости! Если вы пропустили, GitHub Copilot — это уже не отдельный продукт, а набор…
Несколько месяцев назад мы с командой Promodo (агентство инвестировало в продукт более $100 000) запустили…
Пару дней назад прочитал сообщение о том, что хорошие курсы могут стать альтернативой классическому образованию.…