Представим себе ситуацию: аналитики компании Foobar Inc. провели тщательное исследование конъюнктуры рынка и бизнес-процессов компании и пришли к выводу, что для оптимизации издержек и многократного увеличения прибыли Foobar кровь из носу требуется Telegram-бот компаньон, способный подбодрить сотрудников в трудную минуту. Естественно, Foobar не может позволить, чтобы коварные конкуренты воспользовались их ноу-хау, просто добавив их бота себе в контакты. Поэтому требуется, чтобы бот разговаривал только с сотрудниками Foobar, прошедшими аутентификацию в корпоративной системе единого входа (SSO) на основе OpenId Connect. OpenId Connect (OIDC) — это протокол аутентификации, основанный на семействе спецификаций OAuth 2.0. В нем процесс аутентификации может проходить по различным сценариям, называемым потоками (flow), и включает в себя три стороны: Получаемая клиентом в результате аутентификации информация о пользователе представляется в виде JWT-токенов (JSON Web Token). Не будем сейчас углубляться в терминологию и детали спецификации OIDC, поскольку и без того есть немало статей, в том числе и на Хабре, позволяющих составить представление о базовых принципах работы OIDC. Для решения поставленной задачи, помимо реализации основной функциональности бота, нам необходимо реализовать механизм авторизации: при получении каждого нового сообщения от пользователя необходимо проверять наличие и актуальность связки между данным Telegram-пользователем и учеткой в системе SSO. Неавторизованные пользователи при этом должны направляться на страницу аутентификации. Для начала нам необходимо определиться с тем, какой поток аутентификации мы будем использовать. В спецификации OIDC описано несколько возможных сценариев аутентификации пользователей: Поскольку наш бот — это серверное приложение с возможностью доступа по HTTP, нам подойдет поток с кодом авторизации. Схема взаимодействия будет выглядеть следующим образом: В описанном сценарии все хорошо, кроме того, что на последнем шаге, когда бот получил токен доступа и ID токен, у него уже нет информации о том, какой Telegram-пользователь инициировал всю цепочку действий. Здесь самое время вспомнить про параметр state запроса аутентификации, который рекомендуется использовать для предотвращения атак межсайтовой подделки запросов (XSRF). Значение state формируется на стороне бота на шаге 2 и добавляется в URL в виде параметра, затем сервер авторизации на шаге 5 в неизменном виде передает это значение в callback URL бота. Таким образом, если мы на шаге 2 свяжем сгенерированный state с id Telegram-пользователя, то на шаге 7 по значению state сможем сопоставить id Telegram-пользователя с полученными токенами. Бинго! Итак, перейдем к реализации, вооружившись следующими инструментами: Для начала посмотрим на реализацию бота: Далее — определение метода поиска UserInfo по id Telegram-пользователя и метода формирования URL для аутентификации пользователя: Класс, содержащий информацию о пользователе, получаемую от сервера авторизации: Создание и настройка OAuth20Service: Callback endpoint нашего бота: Снова вернемся к OidcService, чтобы посмотреть на реализацию метода completeAuth: Готово! Поставленная задача решена, но за кадром остался еще ряд вопросов, которые необходимо решить, прежде, чем катить это в прод. Например, офлайн-токены необходимо держать в персистентном хранилище, это позволит не направлять пользователей на повторную аутентификацию после каждого перезапуска/обновления бота. Эти вопросы уже решены за нас в более развесистых OAuth-клиентах вроде Spring Security и Google OAuth Client. Но для демонстрационных целей нам и так ок :) Все исходники можно найти на GitHub.