Ory Kratos — конструктор для сборки цифрового продукта любой сложности

от автора

Привет! Я Андрей Баронский, бэкенд-тимлид в KTS.

Одно из ключевых направлений деятельности нашей компании — это аутсорс-разработка цифровых продуктов. При создании очередной системы мы хотим уделять больше времени и сил необходимым фичам для клиентов, а не настройке рутинного взаимодействия с юзерами, и для ускорения проработки основных пользовательских сценариев мы используем технологию Ory Kratos. В статье я расскажу, почему я рекомендую обратить на нее внимание и как с ней работать.

Для тех, кто впервые сталкивается с этим названием, дам немного контекста. Ory Kratos — это система API-first Identity и User Management. Она управляет всеми аспектами работы с пользователями, включая регистрацию, вход, восстановление пароля, многофакторную аутентификацию, верификацию данных и управление профилем. 

Иными словами, Ory Kratos берёт на себя рутинные технические задачи, предлагая готовое, гибкое и удобное в интеграции решение.

Оглавление

Сразу оговорюсь, что на Хабре уже есть статья, посвященная Kratos, и в ней довольно подробно разобрана основная функциональность технологии. Чтобы не повторяться, я не буду заострять внимание на деталях, описанных там. В своем материале я постараюсь больше сосредоточиться на опыте использования и показать, как можно на практике прикрутить Kratos к проекту.

Общая информация

Краткое описание

Ory Kratos распространяется под лицензией Apache-2.0, что даёт свободу его использования, модификации и распространения. Написанный на языке Go, он отличается высокой производительностью, стабильностью и масштабируемостью. Продукт активно развивается, регулярно получая обновления и новые функции. Простота внедрения и отличная документация делают его хорошим выбором для разработчиков.

Разумеется, существуют и альтернативные SSO, но мы отказались от них по своим причинам. 

Мы рассматривали Keycloak (24к звезд на гитхабе) и CAS (11к звезд), однако они нам не подошли, поскольку они написаны на Java и довольно неповоротливы в кастомизации, а ее выполнение требует большого и неудобного стека. К примеру, у Keycloak нет плагина для работы с OTP, и его пришлось бы дописывать самостоятельно.

Также мы смотрели в сторону Authelia (22к звезд) — как и Kratos (11,4к звеад), она написана на Go, и, в целом, тоже подходит под наши задачи. Но в итоге наш выбор пал на Kratos ввиду его простоты, исчерпывающей документации с примерами и возможностями кастомизации UI через разработку.

Почему Ory Kratos: еще несколько причин

Давайте рассмотрим остальные факторы, которые определили наш выбор:

  • наш собственный опыт — ранее мы уже экспериментировали с проектами Ory;

  • наш стандартный технологический стек — мы понимали, что можем без затруднений доработать приложение под собственные нужды;

  • возможность легко кастомизировать клиентскую часть приложения Kratos;

  • наличие нативной авторизации через клиентский API;

  • синергия Kratos с Hydra (провайдер OAuth 2.0 и ID Connect) и Oathkeeper (API gateway), которые имеют готовые компоненты для интеграции друг с другом без каких-либо доработок. Об этих технологиях я расскажу в следующих статьях;

  • удобная и понятная документация;

  • готовые Helm-чарты для развертывания приложения на k8s-кластере;

  • скорость, с которой можно было поднять пилотную MVP версию приложения;

  • легкость и изолируемость компонентов при дальнейшей замене.

Как работать с Ory Kratos

Верхнеуровневый обзор

Перед тем как перейти к подробному разбору конфигурации Ory Kratos, давайте обозначим сценарий, на котором будет строиться дальнейшее описание. Мы будем рассматривать стандартное приложение, которое включает:

  • SSO (Single Sign-On);

  • веб- и мобильный клиенты для взаимодействия с пользователем;

  • интеграцию с сервисом рассылок (например, для подтверждения email или отправки уведомлений);

  • возможную интеграцию с внешним SSO для обеспечения единой точки входа в приложение;

  • отдельный бизнес-слой, реализующий специфические сценарии приложения.

Для наглядности рассмотрим архитектурную схему, которая поможет структурировать процесс настройки и конфигурации. Опираясь на нее, мы шаг за шагом разберем основные этапы настройки Ory Kratos для работы в таком окружении. Это позволит на практике увидеть, как гибкость системы помогает адаптировать её под разнообразные бизнес-требования и технические сценарии.

[BACKEND] Как собирать бэкенд

Для начала нужно определиться, в каких сценариях будет применяться ваша система. Следует ли предусмотреть возможность регистрации, или это будет закрытая система для сотрудников? Можно ли менять информацию о пользователе, или она пробрасывается автоматически через интеграцию с другой мастер-системой?

Когда мы знаем ответы на эти вопросы, мы можем описать в рамках приложения, в каких сценариях (flow) система будет использоваться. Всего на выбор Kratos дает шесть сценариев:

  • settings (изменение пользовательских данных);

  • recovery (восстановление доступов);

  • verification (подтверждение пользователя);

  • logout;

  • login;

  • registration.

При необходимости ненужные сценарии можно отключить. К примеру, если вы работаете над внутренней системой для сотрудников, то сценарий регистрации вам не понадобится. Это можно указать через конфиг:

kratos.config

selfservice:  flows:    verification:      enabled: false

Пока что в  качестве примера мы рассматриваем клиентское приложение, в котором не используются верификация и восстановление доступов.

Примечание: кроме обмена данными между клиентом и сервером, backend-часть приложения выполняет редиректы. Для этого нужно указать путь до соответствующих разделов веб-приложения:

kratos.config

selfservice:  default_browser_return_url: http://127.0.0.1:4455/welcome  flows:    login:      ui_url: http://127.0.0.1:4455/login

Когда вы определились, какие сценарии вам понадобятся, нужно сформулировать, как будет происходить аутентификация пользователя в системе. Рассмотрим ее на примере «email+пароль». Нужно указать это в конфиге следующим образом:

kratos.config

... selfservice:   methods:     password:       enabled: true ...

Система также предлагает альтернативные методы аутентификации — они могут пригодиться, если вам не подходит вариант «логин+пароль»:

  • Passwordless + OTP/Magic Links;

  • Passkeys и WebAuthN;

  • Multi-factor authentication;

  • Social sign in.

Далее необходимо сконфигурировать криптографию и шифрование системы. Внимание на этом заострять не буду, просто приведу пример, как это может выглядеть в конфиге:

kratos.config

secrets:   cookie:     - PLEASE-CHANGE-ME-I-AM-VERY-INSECURE   cipher:     - 32-LONG-SECRET-NOT-SECURE-AT-ALL  ciphers:   algorithm: xchacha20-poly1305  hashers:   algorithm: bcrypt   bcrypt:     cost: 8

К слову, длительность жизни сессий и OTP гибко настраиваются. Их также можно задать через конфиг.

У бэкенда приложения есть Public и Admin API. В конфиге также нужно указать соответствующие им адреса:

kratos.config

serve:   public:     base_url: http://kratos:4433/   admin:     base_url: http://kratos:4434/

Когда все предыдущие шаги выполнены, остается только сконфигурировать интеграцию с СУБД. Приложение поддерживает работу с реляционными базами данных (PostgreSQL, MySQL, SQLite и CockroachDB). Для ознакомления также доступна возможность работы в режиме in memory:

kratos.config

dsn: memory

Подробнее о сборке бэкенда см.:

[DB] Как собирать юзера для базы данных 

В Kratos пользователь — это ключевая сущность. Описать его можно с помощью документа JSON Schema. Для тех, кто раньше не сталкивался, JSON Schema — это декларативный язык для определения структуры и ограничений JSON.

Нам нужно описать основные атрибуты пользователя, указать идентификатор и способ логина. Это делается с помощью расширенных атрибутов ory.sh/kratos, в которых указываются системные зависимости.

Стоит обратить внимание, что title атрибутов пользователя будут использоваться в UI клиентского приложения. Еще при конфигурировании юзера важно помнить, что сервис Kratos отвечает только за работу с пользователем. Следовательно, в его атрибутах не нужно хранить какую-то специфическую бизнес логику, не относящуюся к домену. Храните только те данные, которые будут необходимы во всех компонентах системы. 

Рассмотрим конфигурацию на примере:

identity.schema.json

{  "$schema": "http://json-schema.org/draft-07/schema#",  "type": "object",  "properties": {    "traits": {      "type": "object",      "properties": {        "username": {          "title": "Username",          "type": "string",          "ory.sh/kratos": {            "credentials": {              "password": {                "identifier": true              }            }          }        },        "name": {          "type": "object",          "properties": {            "first": {              "title": "First name",              "type": "string"            },            "last": {              "title": "Last name",              "type": "string"            }          }        }      }    }  } }

Кроме указанных выше атрибутов, есть еще JSON-атрибуты metadata_public (доступные клиенту данные) и metadata_admin (данные, которые можно получить на уровне сервера). За консистентность данных нужно будет отвечать самостоятельно. Валидации этих параметров нет. 

После того, как мы разобрались с «анатомией» пользователя, в конфиге сервера нужно указать путь до схемы.

kratos.config

identity:   default_schema_id: default   schemas:     - id: default       url: file:///etc/config/kratos/identity.schema.json

Подробнее см.:

[FRONTEND] Как собирать фронтенд

Для реализации своего собственного пользовательского интерфейса есть 3 варианта репозиториев, но основе которых можно это сделать:

В данном примере мы будем использовать Next.js/React-приложение из репозитория. Я предлагаю рассмотреть, как указать переменные окружения для конфигурирования фронтенда. В качестве примера возьмем демо веб-приложение:

environment:  - KRATOS_PUBLIC_URL=http://kratos:4433/  - KRATOS_BROWSER_URL=http://127.0.0.1:4433/  - COOKIE_SECRET=changeme  - CSRF_COOKIE_NAME=ory_csrf_ui  - CSRF_COOKIE_SECRET=changeme

Визуализация работы (preview)

Посмотрим, какого результата можно достичь текущей реализацией. На видео используется максимально приближенный к описанному конфиг:

Схема работы (исходник здесь):

Углубленный обзор

Мы познакомились с базовыми сценариями IdM-решения, и теперь я предлагаю рассмотреть дополнительные возможности инструмента, которые могут пригодиться при интеграции Kratos в проект.

Интеграция с сервисом рассылок

Перед тем, как интегрировать наше приложение с сервисом рассылок, нужно добавить необходимость подтвердить почту в сервисе.

1. Добавляем шаг подтверждения почты во flow верификации:

kratos.config

  flows:     verification:       enabled: true

identity.schema.json (строки 16, 17):

  "properties": {     "traits": {       "type": "object",       "properties": {         "email": {           "type": "string",           "format": "email",           "title": "E-Mail",           "minLength": 3,           "ory.sh/kratos": {             "credentials": {               "password": {                 "identifier": true               }             },             "verification": {               "via": "email"             }           }         }       }     }   }

2. Конфигурируем интеграцию с сервисом рассылок. Это может быть реализовано с помощью протокола SMTP. Для того, чтобы уменьшить объем кода и избыточного контекста, я опущу детали работы с аутентификацией. Отмечу только, что возможности ее настройки в проекте есть.

kratos.config

courier:   smtp:     connection_uri: smtps://test:test@mailslurper:1025

Также в Kratos есть возможность шаблонизации сообщений через Go template engine. Если вы с ними ранее не сталкивались с этим инструментом, тут можно с ним ознакомиться. В этом документе указаны переменные, которые будут переброшены при генерации тела письма.

email.body.gotmpl

Hi, please verify your account by code:  {{ .VerificationCode }}

Если же интеграция с сервисом рассылок через SMTP вам не подходит, у вас свой сервер или вы пользуетесь готовым решением, где работа с шаблонами писем уже реализована с интеграцией через HTTP API, то в системе также предусмотрена альтернативная интеграция через HTTP.

kratos.config

... courier:  delivery_strategy: http  http:    request_config:      url: https://api.sendsrv.com/mail/send      method: POST      body: file:///etc/config/kratos/mail.template.jsonnet      headers:        "Content-Type": "application/json" ...

В случае HTTP-интеграции тело запроса шаблонизируется с помощью конфигурационного языка jsonnet. Аналогично при генерации тела запроса будет передан ctx, из которого можно будет достать необходимые переменные и данные о пользователе. Посмотрим на примере интеграции с сервисом рассылок Unisender:

http_courier_template

function(ctx) {     message: {    recipients: [      {        email: ctx.recipient,        substitutions: {          RECOVERY_CODE: ctx.template_data.recovery_code,        },      },    ],    from_email: "no-reply@yourapp.ru",    template_id: "00000000-0000-0000-0000-000000000000"  } }

В итоге получаем следующий результат:

Подробнее см. документацию:

Пара слов про API-авторизацию (native/browser)

Так как помимо обмена данных Kratos выполняет редиректы, у продукта есть отдельный API для native-клиентов. Разработчики рекомендуют воздержаться от использования native-клиентов в браузерных приложениях, поскольку это это открывает брешь в обороне для CSRF-атак.

Webhooks и как их использовать

В продукте есть возможность кастомизации логики с помощью внешних интеграций через webhook. Для всех основных flow есть хуки before и after, которые позволяют реализовать все необходимые сценации. 

При описании webhook есть возможность сконфигурировать их поведение: до или после выполнения сценария, блокирующий или нет. Это может пригодиться, например, если после успешной регистрации пользователя вам нужно автоматически добавлять его в списки рассылок в отдельном сервисе. Или, наоборот, усложнить регистрацию в систему, добавив дополнительные шаги проверок в других компонентах. Тело запроса описывается аналогично примеру с HTTP-интеграцией рассылок с помощью Jsonnet. 

Более того, в Kratos даже есть возможность обновлять атрибуты identity через webhook — как UI-параметры пользователя, так и metadata (публичные и приватные). 

Рассмотрим ситуацию, при которой мы хотим дополнительно проверить заявку на регистрацию пользователя в другом компоненте нашей системы. Для этого воспользуемся блокирующим after-хуком с парсингом тела ответа, чтобы заводить пользователей в систему только после прохождения проверки.

Расширим конфиг сервиса настройками flow регистрации:

kratos.config

selfservice:  flows:    registration:      after:        password:          hooks:            - hook: web_hook              config:                url: http://wiremock:8443/webhook                method: "POST"                body: "file:///etc/config/kratos/reg_webhook.jsonnet"                response:                  parse: true

Приведем пример ответа, который будет обрабатывать Kratos и информировать о проблеме с регистрацией, и замокаем ответ на вебхук. В данном случае "instance_ptr": "#/traits/email" (4 строка) является атрибутом, с которым ассоциируется ошибка:

{    "messages": [        {            "instance_ptr": "#/traits/email",            "messages": [                {                    "id": 12356,                    "text": "Мы решили не пускать тебя в систему!(",                    "type": "error",                    "context": {                        "value": "Мы решили не пускать тебя в систему!("                    }                }            ]        }    ] }

На видео можно посмотреть пример блокирующего вебхука c информацией об ошибке в UI:

Подробнее см. документацию:

Получение пользовательской информации в сервисах бизнес-логики

Можно, конечно, реализовать мидлвар в наших микросервисах для получения информации о пользователях, но я предлагаю использовать для этого Oauthkeeper — AGW, за которым можно скрыть все компоненты приложения. В нем же можно сконфигурировать, какие пользовательские данные мы будем передавать в микросервисы. Так мы избавляемся от необходимости дублировать интеграцию с Kratos в каждом сервисе и можем легко настраивать это на стороне AGW.

OAUTH2

Стоит отметить, что Ory Kratos из коробки поддерживает вход через внешние сервисы с помощью OAuth2 и OpenID Connect. Вам остается только сконфигурировать их. Не буду погружаться в детали, просто оставлю ссылку на соответствующий раздел документации, если вам интересно узнать об этом больше.

Аутентификация по нескольким id

В Kratos есть возможность настроить несколько identifiers и указать различные варианты аутентификации для них. Зачем это нужно? Например, для того, чтобы пользователь мог логиниться в системе не только по адресу электронной почты, но и по номеру телефона. Рассмотрим этот пример подробнее.

Примечание: здесь я не буду рассматривать механизм инкрементального обновления пользовательской схемы через миграции.

Выше мы уже добавили возможность входить в наше приложение через связку «email + password». Добавим также связку «phone + password». Для этого нужно расширить схему пользователя. Доработаем ее:

Identity.schema.json

 {  "$id": "https://schemas.ory.sh/presets/kratos/identity.email.schema.json",  "title": "Person",  "type": "object",  "properties": {    "traits": {      "type": "object",      "properties": {        "phone": {          "type": "string",          "format": "tel",          "title": "Phone number",          "ory.sh/kratos": {            "credentials": {              "password": {                "identifier": true,              }            }          }        }      }    }  } }

Приятные мелочи

Отдельно хочу подсветить небольшие детали, которые нередко упрощают жизнь при работе с Kratos.

  • Во-первых, в системе есть возможность запускать Cleanup jobs, чтобы чистить устаревшие сессии и освобождать данные с диска. Подробнее про них можно почитать в документации.

  • Во-вторых, разработчики добавили возможность деактивации пользователей. Здесь тоже оставлю ссылку на соответствующий раздел.

  • В-третьих, в Kratos предусмотрена миграция пользователей через import, причем при миграции можно сохранять их прежние пароли. По традиции, детали в документации.

  • Можно задать ограничение на уровне конфига — не более одной активной сессии на пользователя.

  • К слову об активной поддержке и расширении функциональности: пока я писал эту статью, появилась возможность реализовать passwordless flow для логина и регистрации через sms.

Небольшое (ленивое) нагрузочное 

Предлагаю посмотреть, как Kratos справляется с нагрузками. Здесь я не преследую цели провести полноценное испытание в условиях, максимально приближенных к реальным, а просто тестирую технологию «в вакууме», чтобы проверить основные паттерны.

Проверим систему двумя простыми сценариями. Тестировать будем следующую конфигурацию:

  • 1 запущенный инстанс приложения на k8s-кластере;

  • порты проброшены на локальную машину;

  • лимиты по памяти —  512 MB.

Первый сценарий нагрузки

N пользователей (в нашем случае — 50) проходят flow аутентификации из двух шагов: создание flow и отправка данных (логин + пароль).. Задержка между шагами — от одной до трех секунд. Разумеется, в действительности типовая нагрузка будет не такой, но сценарий дает нам представление о том, как сервис будет справляться с наплывом новых пользователей в начале своей работы.

Получаем следующие результаты:

  • С заданной конфигурацией Kratos успевает обрабатывать чуть больше 20 запросов в секунду.

  • Больше половины полученных запросов на пике обрабатываются быстрее, чем за две секунды; 95 % запросов обрабатываются быстрее, чем за четыре секунды.

Второй сценарий нагрузки

Пользователь проходит flow аутентификации и начинает в цикле запрашивать информацию о профиле. Также этот запрос может использовать API Gateway для проверки сессии пользователя. Именно такой и будет основная нагрузка на сервис.

Получаем следующие результаты:

  • Как и предполагалось, пик нагрузки происходит в момент, когда пользователи активно логинятся: 100 одновременных юзеров входят в систему примерно за 20 секунд, на протяжении которых RPS не поднимается выше 100.

  • После того, как все пользователи получают активную сессию, проходит пик нагрузки. При 100 пользователях Response Time для 95% запросов не превышает 400 мс. RPS при этом держится чуть меньше 600.

  • Минимальное время обработки запроса — 30 мс.

Выводы

Судя по результатам, «узкое горлышко» системы — это момент активного наплыва пользователей, которые одновременно хотят залогиниться. С запросами информации о профиле Kratos справляется намного эффективнее при прочих равных. Горизонтальное масштабирование системы (увеличение количества инстансов) может помочь решить эту проблему в критические моменты.

Недостатки Kratos

Разумеется, в работе мы столкнулись и с негативными аспектами системы. Поговорим про некоторые из них.

  • Нет ограничений на отправку кода верификации и восстановления доступов, и сделано это не будет. Нужно самостоятельно решать вопрос с ограничениями на стороне AGW и других интеграций.

  • Нет готовой admin-панели. Ее не очень сложно реализовать самостоятельно с помощью кодогенерации поверх схемы базы данных; к примеру, мы используем django + sdk для интеграции с API. Однако отсутствие готовой панели все же несколько огорчает.

  • Заблокированным пользователям может быть неочевидно, что они заблокированы. Они могут начать восстанавливать пароль или попробовать залогиниться в систему с помощью OTP, получить код от системы, и только при его вводе узнать о блокировке. Можно попробовать это решить с помощью блокирующего before-вебхука с парсингом ответа.

Границы применимости и заключение

Мы испытали Kratos на нескольких проектах и сформулировали ряд выводов, касающихся удобства системы. Я смело рекомендую пользоваться этой технологией, если:

  • вы не хотите тратить время разработчиков на создание своих кастомных велосипедов и согласны пользоваться сценариями, предложенными системой (или готовы вводить кардинальные изменения системы через fork проекта);

  • вас устраивает заложенный UI готового решения;

  • вы готовы завести отдельное реляционное хранилище пользователей и можете синхронизировать его с мастер-данными;

  • вам не нужна интеграция с AD (если нужна, то решение вам не подойдет).

На случай, если система вам интересна и вы захотели протестировать ее самостоятельно, оставляю ссылки на полезные материалы:

  1. Туториал по быстрому стартусоответствующий раздел репозитория на GitHub)

  2. API

  3. Структура валидатора конфига (берите конфиг из Quickstart и уже дальше докручивайте при необходимости)

  4. Helm chart, который можно использовать для начала

  5. Postman collection

Если у вас остались вопросы или какой-то из разделов показался не до конца раскрытым, то приходите в комментарии, с радостью обсудим.

Более конвенциональным разработчикам, которые используют Keycloak, я рекомендую почитать статью моего коллеги из DevOps-юнита о том, как прикрутить к нему Firezone. Также советую ознакомиться с материалами из нашего блога для бэкендеров и DevOps-инженеров:

К слову, мы делимся не только опытом работы с конкретными технологиями, но и управленческими практиками. Тимлидам и руководителям я предлагаю к прочтению материал нашего управляющего партнера о том, как дать разработчикам свободу при деплое приложений и ускорить процессы в команде.

Удачи!


ссылка на оригинал статьи https://habr.com/ru/articles/870454/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *