Привет! Меня зовут Артем Ивлев, и я занимаюсь архитектурой идентификации клиентов банка ВТБ. Наша задача — ответить на вопрос, кто использует наш банковский сервис: мобильный или интернет-банк, голосового помощника или просто один из многочисленных офисов. Для этого есть множество инструментов — и я хочу рассказать про становление одного из них.
Пролог
На дворе 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?
shai_hulud
Если из приложения/браузера украли токен, то и куку унесут. Добавили геморроя разработчикам, точки отказа и никакой дополнительной безопасности.
Понимаю бахнули бы еще подпись внутренней крипто-железкой устройства.
ArtemIvlev Автор
куку HttpOnly, SameSite, Secure можно украсть только если у пользователя браузер подметить на какой-то очень сломанный. В остальных случаях обычным расширением браузера куку уже не украсть.
Мобилка тоже защищена в том числе тем, что при обнаружении рута — банковское приложение просто не работает.
В любом случае это только вершина айсберга. а безопасность обеспечивается множеством техник.