Про побудову флоу авторизації розробники замислюються мало не в кожному проєкті. Єдиного правильного рішення тут немає і бути не може.
Хороший авторизаційний флоу спирається на особливості бізнес-логіки та архітектури конкретного продукту, на цілі клієнта і технічні можливості команди розробки.
Саме про це я детальніше розповім у цій статті. Наведені поради базуються на моєму досвіді.
Про що поговоримо?
Спершу коротко поясню суть проєкту. Він поєднує три продукти єдиного фармацевтичного сервісу. Один застосунок може відстежувати товари в аптеках, інший — моніторити стан пацієнтів. Користувачі сервісу мають вільно користуватися обома продуктами без повторної авторизації.
Налаштувати таку можливість і було нашим завданням. От тільки пермісії на кожному домені для кожного юзера різні. Десь користувач може створювати нові сторінки, десь — лише переглядати наявні. Це стало для нас головною складністю при реалізації авторизаційного флоу. Перш ніж пояснити, як ми втілювали тут принцип 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-токену.
Наведені приклади — це доказ того, що авторизаційний флоу може бути дуже різним. Тому завжди ретельно досліджуйте свій проєкт, намагайтесь зрозуміти, який спосіб авторизації буде найоптимальнішим для конкретного випадку.
Блогер та розробник Джозеф Круз розповів, чому не варто писати ідеальний код та чому це…
Днями я завзято нила про щось ChatGPT (експериментую між сеансами з живим терапевтом). І от…
«Крутіть колесо, щоб отримати знижку до 50%!» «Натисніть тут, щоб відкрити таємничу пропозицію!» «Зареєструйтесь зараз,…
Дуже хочеться робити якісь десктопні апки. Сумую за часами коли всі програми були offline-first, і…
Надсилаючи криптовалюту, багато новачків ставлять запитання: як працюють комісії та чому вони відрізняються в різних…
Нова афера набирає обертів — ось детальний розбір того, як фальшиві потенційні роботодавці намагаються вкрасти…