
Привет, Хабр! Меня зовут Аскар Добряков, я ведущий эксперт направления защиты бизнес-приложений в К2 Кибербезопасность. При внедрении Single Sign-On, на первый взгляд, все должно идти по накатанной схеме: настраиваем федерацию со службой каталогов, связываем identity provider с service provider, проверяем работу токенов — и готово. Но дьявол, как всегда, кроется в деталях. Зачастую приходится решать нестандартные задачи. Например, как быть, если у вас две равноправных организации-партнера со своими требованиями к безопасности, и каждый хочет контролировать аутентификацию пользователей, но при этом пользователи одного партнера должны ходить в систему другого?
В этой статье я расскажу, как мы с Даниилом Золотаревым, инженером нашей практики защиты приложений, проводили разработку кастомного аутентификатора для Keycloak, который позволил элегантно решить техническую задачу, стоявшую перед головной и дочерней организациями. Покажу, как мы обошли ограничения штатной функциональности, не нарушая требований безопасности обеих сторон. В процессе вы увидите технические детали реализации, примеры кода и практические советы по созданию собственных аутентификаторов.
Думаю, статья будет полезна архитекторам, разработчикам и всем, кто интересуется тонкостями реализации сценариев SSO на базе Keycloak.
Противоречивые требования
Тезис: наш заказчик является дочерней организацией крупной компании. Ему требовался доступ к корпоративным системам головной организации.
Антитезис: у головной организации есть собственный каталог пользователей, в котором хранятся пользователи нескольких дочерних организаций и франчайзи, в том числе и нашего заказчика.
Тезис: однако в этом случае пользователям заказчика пришлось бы запоминать две пары логин-пароль, что неминуемо вызвало бы путаницу и связанные с этим проблемы. Поэтому заказчик настаивал на использовании собственного каталога.
Антитезис: но такое решение не подходит головной организации. Безопасники не хотят интегрировать систему с чужим каталогом (что можно было бы легко сделать в Keycloak с помощью механизма User Federation - LDAP), потому что мало ли кто и каких пользователей туда добавит; головная организация хочет сама контролировать состав пользователей.
Тезис: у Keycloak для таких случаев предусмотрен механизм Identity Broker.
Identity Broker подразумевает, что Keycloak в головной организации при нажатии на определенную кнопку на форме аутентификации (например, «авторизоваться с учетной записью дочерней компании») переадресовывает пользователя на другой Keycloak, выступая при этом как client, а после успешной авторизации —- предоставляет доступ к целевой системе. При этом Keycloak в головной организации делает запись у себя, добавляя связь между пользователем во внешнем Keycloak и в своей базе.

Антитезис: однако в рамках данного механизма в рамках Keycloak головной организации при первичном входе пользователя дочерней организации создается связь между учетными записями в двух разных серверах Keycloak. Чтобы подтвердить свою подлинность и создать такую связь, пользователь для проверки должен ввести пароль от каталога дочерней организации и от каталога головной организации. То есть мы возвращаемся к проблеме, возникшей в самом начале с двумя комплектам пар логин-пароль.
Тезис: процесс создания связи определяется с помощью потока аутентификации (flow) в Keycloak головной организации. Мы можем изменить его и убрать проверку с вводом логина-пароля.
Антитезис: но безопасники головной организации теряют контроль над процессом и против такой схемы; нужно добавить проверку в каком-то виде.
В поисках решения
Итого, суть конфликта требований:
1. Дочерняя организации хочет использовать данные аутентификации из своего каталога.
2. Головная организация хочет контролировать доступ к своей системе.
Заказчик был категоричен: пользователи должны аутентифицироваться на портале только один раз со своими учетными данными дочерней организации. Головная компания же хотела сохранить полный контроль над доступом через собственный каталог пользователей и в каком-то виде сохранить проверку пользователей.
Что нам предстояло сделать?
Разработать концепцию доверия между брокером аутентификации в доверенной организации и провайдером у заказчика, а затем поток аутентификации на основе этой концепции.
Нам пришлось основательно поломать голову, чтобы найти компромисс. Сначала заказчик предлагал разработать скрипт для синхронизации учетных данных между Active Directory и Keycloak. Правда, каким образом забирать пароли, проводить аутентификацию и реализовывать линки, мы так и не смогли решить.
Мы проработали этот подход: исследовали структуру базы данных и возможности ручного связывания учетных записей. Несмотря на то, что штатный функционал систем позволял установить необходимые связи между записями, это не решало проблему двойной аутентификации.
Решение нашлось в архитектуре Keycloak: система предоставляет настраиваемые потоки аутентификации (authentication flow) с возможностью определения последовательности проверок – пароля, cookies, UTP и других параметров и проверок. Опираясь на наш предыдущий опыт разработки кастомных аутентификаторов, мы приняли решение реализовать проверку пользователей через кастомные пользовательские атрибуты.
Как выглядит концепция доверия, согласованная в итоге всеми сторонами?
У каждого пользователя в каталоге заказчика создается дополнительное поле — идентификатор (username) в каталоге головной организации.
У каждого пользователя в каталоге головной организации есть также дополнительное поле — идентификатор организации, к которой этот пользователь относится.
Если при первичном подключении пользователя совпадает оба фактора — дополнительное поле в каталоге дочерней организации соответствует имени пользователя в головной, и в каталоге головной организации для этой учетной записи указан корректный идентификаторы дочерней организации, — связь для этой учетной записи можно создавать без других проверок.

Мы написали кастомный блок с валидацией дополнительного атрибута «Идентификатор организации», уникального идентификатора каждой франшизы в системе, а также сверки логинов пользователей в двух системах
Для демонстрации концепции развернули тестовый стенд Keycloak. Заказчик посчитал такое решение наиболее удачным, потому что в целом оно штатное — используется стандартный функционал identity broker, немного доработанный в части flow создания учетных записей. То есть вся база построена на уже отлаженных возможностях системы, а кастом только там, где его предполагает Keycloak. В общем, все по best practice.
Партнеры проанализировали наше предложение, и наконец утвердили проект и согласовали начало работ по внедрению.
Разработка кастомного аутентификатора и принцип его работы
Архитектура решения базируется на организации защищенной связи между двумя автономными каталогами пользователей. В каталоге головной организации уже был атрибут, отвечающий за организацию дочерней организацией. Параллельно в каталоге дочерней организации для каждого пользователя сохраняется соответствующий ему идентификатор из каталога — логин из системы головной организации.
Таким образом, когда пользователь первый раз авторизуется в системе головной организации, пароль у него не запрашивается, но незаметно для него выполняются следующие проверки:
Есть ли пользователь с таким логином, который указан в дополнительном атрибуте, в каталоге головной организации?
Если есть — какая дочерняя организация прописана для него, соответствует ли это нашей организации?
Такая система перекрестных ссылок обеспечивает надежную идентификацию пользователей и позволяет однозначно сопоставлять учетные записи между разными доменами.
Для реализации мы разработали кастомный аутентификатор. О том, как добавить его в систему аутентификации, мы уже рассказывали в отдельной статье.Технически это плагин, наследующий базовый класс аутентификаторов Keycloak. На его основе мы создали и скомпилировали собственный модуль, который интегрируется в поток аутентификации Keycloak “First broker login”.
При первой попытке пользователя подключиться к системе головной организации через Keycloak с учетной записью заказчика система выполняет двухэтапную проверку:
проверяется существование пользователя в системе головной организации и соответствие его данных с данными подключающегося пользователя;
система анализирует наличие пользователя в каталоге дочерней организации и сверяет его идентификатор с записью у головной.
После успешного прохождения обеих проверок система связывает учетные записи и предоставляет пользователю доступ.
Создание потока аутентификации и его интеграция в Keycloak

Наше решение потребовало создания потока аутентификации.
Пользователь идет на портал (целевой системы) → Портал переадресовывает его на Keycloak головной компании.
В форме аутентификации пользователь может выбрать способ авторизации: с учетной записью головной организации или данными из каталога дочерней. Наш сценарий — из каталога дочерней.
При выборе этого варианта Keycloak головной компании начинает работать в режиме Identity Broker и переадресовывает пользователя на Keycloak, который находится в контуре заказчика, выступая при этом как client.
После успешной авторизации на Keycloak в контуре заказчика пользователь переадресовывается обратно на Keycloak головной компании (Identity Broker).
Если это не первичный вход → У пользователя уже есть учетная запись в Keycloak головной компании. Для нее не задан пароль, но указана связь с вышестоящим Keycloak.
Первичный вход → Создаем нового пользователя.
Помним про ограничения службы безопасности головной компании, поэтому перед созданием пользователя проходят дополнительные проверки:
a. что в каталоге заказчика-дочерней компании для учетной записи указан username из каталога головной компании.
b. что в каталоге головной компании есть пользователь с таким username, и для него указана организация, соответствующая заказчику-дочерней компании.
8. Если эти условия выполняются, в Keycloak головной компании (он же Identity Broker) создается учетная запись без пароля и со ссылкой на вышестоящий Keycloak. Далее авторизация этого пользователя проходит уже как постоянного посетителя портала.
Теперь рассмотрим стандартный поток аутентификации на брокере.

Create User If Unique — на этом шаге проверяется, существует ли учетная запись Keycloak с тем же адресом электронной почты или именем пользователя, что и у учетной записи поставщика удостоверений. Если учетная запись не существует, аутентификатор создает новую локальную учетную запись Keycloak, связывает ее с поставщиком удостоверений, и процесс завершается. В противном случае система переходит к следующему потоку обработки существующей учетной записи.
Чтобы избежать появления дублирующихся учетных записей, можно пометить этот аутентификатор как обязательный. Тогда пользователь с уже существующей учетной записью Keycloak увидит страницу с ошибкой и должен будет связать свою учетную запись поставщика идентификационных данных через управление учетной записью.
Handle existing account — подпоток, который обрабатывает существующие учетные записи с тем же адресом электронной почты или именем пользователя.
Account verification options — подпоток, который верифицирует учетные записи с одинаковым адресом электронной почты и именем пользователя.
Verify Existing Account by Re-authentication — предназначен для связывания пользователей в IdP и IdBroker.
Username Password Form For IdP — обеспечивает повторную аутентификацию с учетными данными пользователя из брокера.
Тестирование решения, поиск багов и проблема недостаточной документации Keycloak
Основная сложность была в извлечении атрибута пользователя Franchise ID из UserInfo. Проблема крылась в недостаточно подробной документации Keycloak по работе с пользовательскими объектами. Она дает общее понимание структуры данных, но точный механизм получения атрибутов и их формат толком не описывает.
Мы опробовали различные методы извлечения данных, постепенно определяя правильный подход через серию экспериментов. Этот опыт показал, что даже при работе с популярными инструментами иногда приходится полагаться на метод научного тыка.

После ряда экспериментов, мы взяли класс IdpCreateUserIfUniqueAuthenticator за основу для кастомного аутентификатора. В его исходном коде есть комментарий, который говорит, что мы можем сами доработать логику и сопоставлять учетные записи не только по username или email, но и по другим атрибутам.


Основные сложности возникли при синхронизации каталогов организаций. В некоторых случаях аутентификатор был неверно привязан к каталогу, иногда некорректно указывался «Идентификатор организации». В таких ситуациях аутентификатор блокировал запросы, что подтверждало корректность его работы с точки зрения безопасности.
Мы последовательно устраняли выявленные недочеты, находя оптимальные или компромиссные решения. Например, дочерняя компания взяла на себя обязательство по регулярному обновлению идентификаторов в своих каталогах.
В заключение отмечу, что кастомизация Keycloak — действительно мощный инструмент для решения нестандартных задач аутентификации. Мы убедились, что даже при конфликтующих требованиях к безопасности c его помощью можно найти элегантное решение, удовлетворяющее все стороны.
Надеюсь, эта статья поможет вам в реализации собственных проектов на базе Keycloak.