Привет! Меня зовут Артем Ивлев, и я занимаюсь архитектурой идентификации клиентов банка ВТБ. Наша задача — ответить на вопрос, кто использует наш банковский сервис: мобильный или интернет-банк, голосового помощника или просто один из многочисленных офисов. Для этого есть множество инструментов — и я хочу рассказать про становление одного из них. 

Пролог

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

Требований к тому, как все должно выглядеть, у нас еще не было. При этом мы сразу же начали говорить об аутентификации физических лиц не только в онлайн-банке, но и на ресурсах партнеров. Та самая кнопочка «Войти через ВТБ». 

Примерно так будет выглядеть вход через ВТБ

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

Выбираем, по какому пути пойдем

Прошерстив интернет, покурив магические квадраты Гартнера, начали смотреть на опенсорс-решения с поддержкой в России:

• WSO2 Identity Server

• Keycloak

• OpenAM

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

Архитектура WSO2 Identity Server

Первый прототип мы показали еще в 2019 году на общем выездном демо розничного бизнеса.

Серебряная пуля есть (извини, Брукс) — это токен

В первую очередь мы поняли, что в OAuth 2 нам лучше всего использовать ID-токен JWT, который не требует обращения к серверу аутентификации для проверки при каждом запросе. Если вкратце, JWT — это пирамидка из трех частей:

1. Заголовок (HEADER) рассказывает, что это за токен, как подписан и кем выдан.

2. Тело (PAYLOAD) — набор параметров — позволяет узнать, какому сервису и для какого пользователя выдан этот токен, как долго этот токен будет жить.

3. Криптографическая подпись (SIGNATURE) дает возможность проверить, что данные в теле токена не были изменены и что токен выдан именно тем сервером идентификации, которому мы доверяем.

Структура JSON Web Token (JWT)

Все эти части — это одна большая строка, разделенная точками на блоки. Первые два блока кодированы с помощью алгоритма Base64.

Использование ID-токена JWT сильно упростило жизнь: больше не нужно было при каждом запросе от мобильного устройства или браузера ходить в базу и проверять, кому и когда выдан токен. Вся информация — в самом токене. Достаточно просто поставить перед потребителями API Gateway, который проверит подпись токена с помощью открытого ключа.

Как доработать пулю напильником

Токен, выпущенный на X минут, будет действителен, даже если пользователь нажмет «Выход» или от систем службы безопасности банка поступит сигнал срочно разлогинить этого пользователя из приложения. 

Решение давно придумали — хранить списки отозванных токенов и сверяться с ними на уровне API Gateway. Да, это тоже сверка с базой, но здесь множество существенно меньше и легко реализуется на кеше Redis с TimeToLive.

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

Кадр из заставки мультсериала «Симпсоны»

Как обычно, задача не нова и ее решение «уже было в "Симпсонах"» ((с)? «Южный парк»). Делаем безопасную куку (HttpOnly, SameSite, Secure) и кладем в нее UUID. Потом от этого UUID делаем хеш, например, CRC32, — и кладем его уже в тело JWT. И все. Теперь достаточно для каждого запроса проверять, что хеш от нашей куки равен тому, который записан в тело токена. Безопасную куку утащить сложнее, так что пара «токен — кука» дает нам достаточно уверенности. 

Адаптивная аутентификация, или Почему нам пришлось идти своим путем 

В процессе входа пользователя много дополнительной магии. Это и проверки пользователя, его устройства, статистики по входам, географии, и аудит действий, и открытие сессий в бэк-системах, и уточнение необходимости второго фактора, и выбор этого самого второго фактора (СМС, push, что-то еще).

Это и есть задача адаптивной аутентификации. И, с одной стороны, использование модуля позволило все эти задачи вписывать с помощью скриптового интерфейса WSO2 IS, но, с другой стороны, невозможно было реализовать красивый API для мобильных устройств или SPA. 

Дело в том, что вся адаптивная аутентификация идет на серверном уровне как генерация интерфейсов с помощью JSP. Это сложно адаптировать даже для современной веб-разработки, не говоря уже о API для мобильного приложения.

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

Для мобильного приложения и интернет-банка мы решили реализовать всю логику с помощью всего лишь одной ручки API/oauth2/token — в зависимости от входящих параметров она выдавала токен или ошибку с просьбой перейти на нужный шаг запроса второго фактора. 

Реализацию бизнес-логики решили внести прямиком в grant_type, параметр /oauth2/token, отвечающий на вопрос о нужном типе аутентификации.

В итоге у нас получилась стройная логика. Есть сервис-провайдер — потребитель аутентификации. У него есть настройки, среди которых — набор grant type. Тем самым мы отлично понимаем, кто и с помощью каких логик входа к нам может прийти. 

Все, что не роняет прод, делает нас сильнее

Следующая проблема, которую мы поймали, — невысокая производительность из-за довольно сложного использования базы данных для сценария аутентификации, в котором мы контролируем «что и почему» на уровне нашего кода. WSO2 IS слишком много читает и пишет в базу. 

Пришлось нам снова переписывать часть логики генерации токена. Убрали сессии из PostgreSQL в Redis. Это на порядок снизило нагрузку на базу. 

Комментарии излишни:)

Но, как оказалось, этого тоже мало. Сама зависимость от базы для столь критичного сервиса — далеко не лучшее решение. В случае падения или недоступности базы (где хранятся в основном настройки и выданные токены, сессии-то мы уже оттуда убрали) мы не могли пустить пользователя в банк. 

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

Следующим шагом был переход от двух ЦОДов в режиме active-active к режиму active-passive, что снизило риски рассинхрона PostgreSQL и Redis. В худшем случае при падении ЦОД какой-то части пользователей придется перезайти в приложение.

Ну и как финальный гвоздь в код выдачи токенов — решение полностью переписать остатки WSO2 IS. Последняя версия уже вообще не общается с базой при аутентификации пользователя. Остался только Redis, который хранит сессии аутентификации и выданные в них JWT и refresh-токены. 

Что дальше? Новые гориSSOнты

При выдаче токена осталось так мало от вендорного WSO2 IS, что в скором времени мы сможем перейти от монолита, который умеет слишком много, к микросервисам, которые умеют только то, что нам нужно и с нужной нам производительностью. 

Сейчас у нас есть вход в банковское приложение в мобильной и веб-версиях. Но этого мало. Ведь у ВТБ есть множество других продуктов. Это «Мультибонус», «ВТБ Мобайл» и десятки, если не сотни, других наших проектов. Для всех требуются аутентификация и единая точка входа. 

Кадр из мультсериала «Рик и Морти»

И еще есть масса внешних партнеров и поставщиков, которые, конечно, только обрадуются «четким» пользователям с паспортом и прочими данными. Поэтому мы работаем над единым логином ВТБ. Пользователю он даст возможность идентифицировать себя в каршеринге, страховой, стриминге или «Рогах и копытах» максимально безопасно и удобно (согласитесь, проще нажать на кнопку «Войти через ВТБ», а не фотографировать паспорт и вбивать данные вручную). А потребителю аутентификации ВТБ — очень удобный путь идентификации и данные пользователей в соответствии с федеральным законодательством.

Эпилог

Скоро год, как наши первые релизы ушли в прод. Решение развивается и обрастает новыми возможностями. Не стоило ли нам сразу начать писать свое? Нужен ли вообще был WSO2 IS? 

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

ЗЫ: Буду рад обсудить вопросы по SSO, токенам и вообще аутентификации.

ЗЗЫ: Про что хотите почитать в следующий раз? Аутентификацию, биометрию или цифровой профиль и использование Tarantool Data Grid?