
Что может быть общего у разработчика из крупной московской ИТ-компании и пенсионерки из Вологодской области? Ну, например, они оба регулярно пользуются SSO — технологией единого входа. Разработчик входит под одной учеткой во все корпоративные рабочие системы, а пенсионерка авторизуется через «Госуслуги» — чтобы записаться к врачу, проверить пенсию или оплатить коммунальные услуги. Об этом и поговорим, в смысле, об SSO, а не о «Госуслугах».
Привет, Хабр! Я Денис Радостев, старший backend-разработчик в IBS. В этой статье расскажу о Blitz Identity Provider, российской платформе управления цифровой идентичностью, которая обеспечивает единый вход — SSO, многофакторную аутентификацию и централизованное управление пользователями, и как ее можно интегрировать с приложениями Java по протоколу OAuth 2.0. Покажу ключевые настройки Blitz IDP и моменты, которые важно учитывать на стороне Java-приложения.
А вместо заключения расскажу про наш собственный кейс, связанный с синхронизацией пользователей между Blitz IDP и нашей старой БД для самописной авторизации.
Особенности Blitz Identity Provider
Ключевая фишка Blitz IDP — это поддержка современных протоколов: OAuth 2.0, OpenID Connect, SAML 2.0/WS-Federation и даже Radius. Это делает его универсальным мостом между корпоративными приложениями, написанными на любых языках и технологиях.

В этой статье я сфокусируюсь на связке Blitz IDP версии 5.11.3 и Java Spring Boot через стандарт OAuth 2.0.
OAuth 2.0 в Blitz IDP
Прежде чем писать код, нужно познакомить Java-приложение с Blitz IDP. Это можно сделать с помощью четырех шагов:

-
регистрации приложения в консоли администрирования Blitz IDP;
-
генерации секретного ключа Secret ID;
-
настройки редиректа;
-
выдачи разрешения на приложение.
В консоли администрирования Blitz IDP надо перейти в раздел «Приложения» и нажать «Добавить приложение». После чего в базовых настройках указываем Client ID.
На скриншоте ниже есть уже добавленные приложения. Их названия чаще всего указываются на русском, а чуть ниже указывается Client ID.

В добавленном приложении мы выбираем протокол OAuth 2.0, и система генерирует секретный ключ client_secret. Это первый параметр на скриншоте ниже:

Критически важно для безопасности указать redirect_url — адрес, куда Blitz будет перенаправлять пользователя после логина. Без него авторизация работать не будет. На скриншоте редирект указан предпоследним параметром, это префиксы ссылок возврата (как с https, так и без).
Последним шагом мы определяем, к каким именно данным пользователя приложение получит доступ. Список допустимых разрешений внизу скриншота. Здесь указаны usr_grps, blitz_groups, openid и profile.
После настройки приложения можно переходить к самому интересному — к коду.
Интеграция с Java (на примере Spring Boot)
Покажу интеграцию на примере Spring Boot.
Мы должны добавить стандартный стартер для OAuth-клиента.

А в application.yml указываем настройки для подключения Blitz — CLIENT_ID, тот же, что и при регистрации приложения, и сгенерированный BLITZ_CLIENT_SECRET — секретный ключ. Также обязательно указываем redirect-uri и scope.

В самом низу указаны стандартные адреса Blitz, с помощью которых будет работать авторизация.
Настройка Spring Security
Настройка Spring Security довольно простая. Достаточно подключить OAuth 2.0, а все остальные параметры подтянутся из application.yml.

Получение данных пользователя
После входа в систему данные пользователя можно получить через следующий эндпойнт:

Мы можем получить стандартные данные: ФИО, почту, номер телефона и список групп, в которых пользователь состоит.

Обратите внимание на поле sub. Это внутренний ключ нашего пользователя в системе Blitz.
Валидация токенов и безопасность
Чтобы правильно идентифицировать пользователя и выдать ему доступ к системе, необходимо свалидировать токены. Это можно сделать тремя способами.
Самый первый, он же самый простой, — проверка подписи (JWT). Sprint Security с помощью application.yml сам подгружает публичные ключи с JWKS URI Blitz IDP и верифицирует подпись каждого access token.
Второй способ также работает через Spring Security, но с дополнительными настройками. Это валидация claims. Для ее выполнения необходимо настроить introspection (интроспекцию). Метод выполняется дольше, но он способен автоматически отзывать истекшие токены (refresh выполняется через refresh token).
Третий способ — свой собственный, кастомный. Мы сами обращаемся к Blitz через REST API запросы и валидируем токен. Мы можем написать свою логику валидации с учетом специфичных claims для нашего приложения.
Logout-логика
Для выхода из системы опять же требуется настройка Blitz. Требуется опять зайти в консоль администрирования и указать редирект. На скриншоте ниже это второй параметр.

В наших проектах мы также поставили галочку, чтобы при выходе из системы Blitz не запрашивал у пользователя подтверждения.
Со стороны Java в Spring Security необходимо просто настроить редирект.

Если что, в качестве примеров я использовал фрагменты разных проектов, поэтому редиректы в коде отличаются.
Синхронизация пользователей из Java-приложения
В качестве примера из собственного опыта расскажу историю, как мы настраивали синхронизацию пользователей между Blitz IDP и нашей старой БД для самописной авторизации
Авторизацию переписывали несколько раз. Сначала она была самописной, потом перешли на ЕСИА, а сейчас сделали все через Blitz. В такой схеме изначально данные пользователей, коих было очень много — больше миллиона, хранились в нашей же БД. Требовалось перенести все это в Blitz, так как без данных ничего бы не заработало, и попутно разослать всем новые логины и пароли — у Blitz были настроены более строгие правила для паролей.
Зарегистрировать такое количество пользователей вручную нереально. Поэтому мы написали кастомную синхронизацию пользователей между нашей БД и Blitz.
Здесь видно, как мы берем пользователя из базы, проверяем, есть ли он в Blitz, и при необходимости регистрируем.
//Берем всех пользователей из БД List<UserBlitz> userBlitzs = userBlitzRepository.findAll(); for (UserBlitz user : userBlitzs) { //Проверка, если не нашли пользователя в системе Blitz if (!getUser(user.getId())) { //Регистрируем пользователя в системе createUser(user); //Если у пользователя есть группы if (user.getRegion() != null) { String groupId = String.format(FORMAT_REGION_ID, user.getRegion().getRegionCode()); GroupDto groupDto = getRegion(groupId); //Если такой группы не существует в системе Blitz if (groupDto == null) { //Создаем группу createRegion(groupId, user.getRegion().getSubjectName()); } //Добавляем пользователя в группу addUserInGroup(groupId, user.getId()); } } }
У Blitz предусмотрены и ролевая модель, и связь с регионами, но мы пошли нестандартным путем и все это реализовали через группы. Поэтому после проверки пользователя мы также проверяем роль, если данной группы в Blitz нет, то создаем ее и добавляем пользователя в группу.
Ниже идут два метода: первый — для получения пользователя, второй — для регистрации в нашей системе.
private Boolean getUser(String sub) { HttpHeaders headers = new HttpHeaders(); headers.set("Authorization", "Basic " + toBase64(config.getClientId(), config.getClientSecret())); ResponseEntity<String> response = restTemplate.exchange(config.getFindUserURL(sub), HttpMethod.GET, new HttpEntity<>(headers), String.class); return !response.getBody().equals("[]"); }
@SneakyThrows private void createUser(UserBlitz user) { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); headers.set("Authorization", "Basic " + toBase64(config.getClientId(), config.getClientSecret())); HttpEntity<String> entity = new HttpEntity<>(new ObjectMapper().writeValueAsString(parseUser(user)), headers); restTemplate.exchange(config.getCreateUserURL(), HttpMethod.PUT, entity, String.class); }
Обратите внимание на header, где прописана авторизация. Blitz просит авторизацию Basic, которая состоит из ClientId и ClientSecret-приложения.
@SneakyThrows private void createRegion(String groupId, String groupName) { GroupDto groupDto = new GroupDto(); groupDto.setId(groupId); groupDto.setName(groupName); groupDto.setProfile("orgs"); ObjectMapper mapper = new ObjectMapper(); restTemplate.exchange(config.getCreateGroupUrl(), HttpMethod.POST, getHeaderWithBearer(mapper.writeValueAsString(groupDto)), GroupDto.class); }
А вот код для создания группы. Здесь мы используем хедеры с авторизацией Bearer, где мы также получаем CredentialsToken, это отдельный метод.
private HttpEntity<String> getHeaderWithBearer(String body) { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); headers.set("Authorization", "Bearer " + getCredentialsToken()); return new HttpEntity<>(body, headers); }
Вот он:
public String getCredentialsToken() { // Заголовки HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); headers.set("Authorization", "Basic " + toBase64(config.getClientId(), config.getClientSecret())); // Параметры запроса String body = "grant_type=client_credentials&scope=blitz_groups"; // Создаем сущность запроса HttpEntity<String> entity = new HttpEntity<>(body, headers); // Выполняем запрос ResponseEntity<TokenDto> response = restTemplate.exchange(config.getBlitzTokenEndpoint(), HttpMethod.POST, entity, TokenDto.class); if (response.getBody() != null) { return response.getBody().getAccessToken(); } else { throw new AuthException("Произошла ошибка при авторизации"); } }
Этот токен можно получить из самого Blitz, но для этого надо указать авторизацию Basic, снова передать ClientId, ClientSecret и разрешение.
Мне показалось интересным, что для создания и регистрации пользователя достаточно basic, но для создания и получения группы нужна авторизация bearer, которая также получается через basic. То есть для работы с группами происходит двойная авторизация, а для работы с самим пользователем — одинарная. Почему так сделано — для меня загадка. В документации это не расписано.
Еще один интересный момент. В Blitz есть генерация паролей и рассылка писем. Но они работают не так, как хотелось бы.
Мы можем создать пользователя и не придумать ему пароль самостоятельно, а просто нажать на кнопку «генерация пароля». Но работает она только из консоли Blitz. Пароль скопируется в буфер обмена и его можно куда-нибудь отослать либо где-то сохранить. Однако когда мы обращаемся к Blitz из Java, генерация не работает. В итоге нам обязательно нужно сгенерировать пароль на стороне Java.
С рассылкой примерно та же история. В данных пользователя Blitz есть адрес электронной почты, поэтому кажется, что можно воспользоваться рассылкой. Но как мы ни пытались ее настроить, передать логин или пароль не получилось. Поэтому логины и пароли мы получали через собственную реализацию, с ее же помощью осуществляли рассылку на почту.
Личные впечатления от опыта работы с Blitz IDP
Нам удалось легко и быстро настроить интеграцию через Spring Security. Также Blitz позволяет сделать кастомную реализацию, чем мы и воспользовались. Это помогло быстрее перенастроить работу со старой самописной авторизации на Blitz.
Однако мы заметили, что, если в настройках Blitz что-то меняется, лучше сразу перегружать всю связанную с ним систему. У нас очень много проблем возникало из-за того, что мы меняем какой-то один параметр в консоли, пытаемся что-то получить, но ничего не работает. Да, нужна полная перезагрузка системы, только после этого Blitz подгружает параметры.
В самом Blitz есть генерация пароля и отправка писем, но работает этот функционал с нюансами, о которых я рассказал.
И последнее: Blitz — живой, активно развивающийся продукт. Его все еще разрабатывают. При этом у нас на проектах используется сертифицированная версия. Она одна и не является самой свежей. В интернете чаще всего есть информация о более новых версиях, поэтому нам приходится постоянно обращаться к документации (благо она на русском).
ссылка на оригинал статьи https://habr.com/ru/articles/1043914/