Методические рекомендации по использованию Единой системы идентификации и  аутентификации. Версия 2.54
Методические рекомендации по использованию Единой системы идентификации и аутентификации. Версия 2.54

Введение

В эпоху цифровизации обеспечение надежной аутентификации и авторизации пользователей становится основой для безопасного доступа к различным государственным сервисам. Единая система идентификации и аутентификации (ЕСИА) предоставляет инструменты для выполнения этих задач в России. В этой статье я поделюсь опытом интеграции с ЕСИА с использованием OpenID Connect 1.0, а также предоставлю пример реализации интеграции на Java с ЕСИА для получения персональных данных пользователя. 

Зачем нужна интеграция с ЕСИА?

ЕСИА — это централизованная государственная платформа, предназначенная для идентификации и авторизации граждан при доступе к государственным и коммерческим сервисам. Интеграция с ЕСИА позволяет:

  • Аутентифицировать пользователей через их учетные записи ЕСИА.

  • Получать детальную информацию о пользователях: ФИО, пол, СНИЛС, ИНН, паспорт и другие данные.

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

Общая схема работы

Аутентификация и авторизация с ЕСИА состоит из нескольких этапов:

  1. Подготовка запроса на аутентификацию:
    Клиентская система направляет запрос в ЕСИА с параметрами, такими как client_id, redirect_uri, scope.

  2. Аутентификация пользователя:
    ЕСИА проверяет учетные данные пользователя и перенаправляет его обратно в клиентскую систему с авторизационным кодом. Важный момент – пользователь должен подтвердить отправку скоупов персональных данных.

  3. Обмен авторизационного кода на токен:
    Клиентская система обменивает код на идентификационный токен (id_token) или маркер доступа (access_token).

  4. Валидация и использование токена:
    После проверки токена клиентская система может запрашивать персональные данные пользователя через REST API ЕСИА.

Простое объяснение:

  • Формируем запрос на получение авторизационного кода.

  • Перенаправляемся на сайт esia.gosuslugi.ru.

  • Пользователь авторизуется и подтверждает передачу данных.

  • Получаем токен и забираем персональные данные.

Реализация на Java

Для работы с p12-сертификатом будем использовать библиотеку BouncyCastle версии не ниже 1.66. В более ранних версиях есть проблемы с поддержкой алгоритма GOST3411-2012, который используется для подписи сообщений на Госуслугах.

Экспорт ключа КриптоПро в p12

Перед началом работы необходимо экспортировать флешку с ключом КриптоПро в p12 (PFX) контейнер. Это позволит избежать лишних затрат на лицензии КриптоПро.

Подробнее о процессе можно прочитать в моей статье:
Экспорт ключа КриптоПро в p12.

Будем считать с преобразованием ключа в p12 мы справились, идем дальше.

Получение авторизационного кода

Система-клиент должна направить пользователя на страницу предоставления прав доступа в ЕСИА:

Адрес:
https://esia.gosuslugi.ru/aas/oauth2/ac

Параметры:

  • client_id — идентификатор системы-клиента.

  • client_secret — подпись запроса в формате PKCS#7, закодированная в Base64.

  • redirect_uri — URL, куда будет перенаправлен пользователь после аутентификации.

  • scope — область доступа (например, openid fullname birthdate).

  • response_type — тип ответа (code).

  • state — случайный идентификатор запроса.

  • timestamp — время запроса (формат yyyy.MM.dd HH:mm:ss Z).

Если в ходе аутентификации не возникло ошибок, то ЕСИА осуществляет редирект
пользователя по ссылке, указанной в redirect_uri, а также возвращает два обязательных
параметра:
<code> – значение авторизационного кода;
<state> – значение параметра state, который был получен в запросе на аутентификацию;
система-клиент должна провести сравнение отправленного и полученного параметра state.

Пример запроса:

https://esia.gosuslugi.ru/aas/oauth2/ac?timestamp=2015.11.27+13%3A03%3A52+%2B0300&scope=openid&client_secret=MIIFpgYJKoZIhvc…&response_type=code&redirect_uri=https%3A%2F%2Fesia.gosuslugi.ru%2Faas%2Foauth2%2Ftest%2FoauthCallback.xhtml&state=f21125b6-60e2-4edc-a0ab-e7da2d31708f&client_id=TESTSYS


В случае ошибки сервис авторизации вернет в параметре error код ошибки (например,
“access_denied”) и перенаправит пользователя по адресу, указанному в redirect_uri. 

Давайте реализуем на java механизм формирования URL для запроса кода авторизации:

public String getLoginUrl() throws Exception {

    String state = UUID.randomUUID().toString();

    String timestamp = getEsiaDateNow();

    String clientMsg = scope + timestamp + clientId + state;

    byte[] signMessage = signerService.signMessageGost(clientMsg.getBytes());

    byte[] clientSecret = Base64.getEncoder().encode(signMessage);

    String params = "?timestamp=" + URLEncoder.encode(timestamp, "UTF-8") +

                    "&scope=" + scope +

                    "&client_secret=" + URLEncoder.encode(new String(clientSecret), "UTF-8") +

                    "&response_type=code" +

                    "&redirect_uri=" + redirectUrl +

                    "&state=" + state +

                    "&client_id=" + clientId;

    return esiaSiteAuthCode + params;

}

Особое внимание обратим на параметры: 

redirect_uri – это адрес нашего портала, куда мы вернемся после подтверждения пользователя о предоставлении доступа к его персональным данным.

И параметр timestamp – он должен быть обязательно в формате ‘yyyy.MM.dd HH:mm:ss Z’ пример 2024.10.08 22:30:52 +0300 и к тому же закодирован URLEncoder

Этот параметр используется для подписи, если формат будет хоть в чем-то отличаться, запрос будет не валидным!

Если с подписью и формированием атрибутов все ок, после авторизации получим запрос на предоставление доступа со списком скоупов: 

После предоставления доступа произойдет редирект на redirect_url с полученным кодом авторизации. Он то нам и нужен для получения токена!

Получение токена в обмен на авторизационный код


Когда авторизационный код получен, система-клиент может сформировать запрос методом POST в адрес ЕСИА для получения маркера идентификации.

Адрес:
https://esia.gosuslugi.ru/aas/oauth2/te

Один авторизационный код можно обменять на один маркер идентификации. 

В тело запроса должны быть включены следующие сведения:
<client_id> – идентификатор системы-клиента (мнемоника системы в ЕСИА указанная прописными буквами) - Weblp

<code> – значение авторизационного кода, который был ранее получен от ЕСИА и который необходимо обменять на маркер идентификации;

<grant_type> – принимает значение “authorization_code”, если авторизационный код обменивается на маркер идентификации;

<client_secret> – подпись запроса в формате PKCS#7 detached signature в кодировке UTF8 от значений четырех параметров HTTP–запроса: scope, timestamp, clientId, state (без разделителей). <client_secret> должен быть закодирован в формате base64 url safe. Используемый для проверки подписи сертификат должен быть предварительно зарегистрирован в ЕСИА и привязан к учетной записи системы-клиента в ЕСИА. ЕСИА поддерживает сертификаты в формате X.509. 

<state> – набор случайных символов, имеющий вид 128-битного идентификатора запроса (необходимо для защиты от перехвата), генерируется по стандарту UUID; этот набор символов должен отличаться от того, который использовался при получении авторизационного кода;

<redirect_uri> – ссылка, по которой должен быть направлен пользователь после аутентификации (то же самое значение, которое было указано в запросе на получение авторизационного кода); https://szr.rt.ru/

<scope> – область доступа, т.е. запрашиваемые права (то же самое значение, которое было указано в запросе на получение авторизационного кода); openid fullname birthdate gender snils inn id_doc

<timestamp> – время запроса маркера в формате yyyy.MM.dd HH:mm:ss Z (например, 2013.01.25 14:36:11 +0400), необходимое для фиксации начала временного промежутка, в течение которого будет валиден запрос с данным идентификатором (<state>);

<token_type> – тип запрашиваемого маркера, в настоящее время ЕСИА поддерживает только значение “Bearer”.

Пример ответа:
{“id_token”:
“eyJhbGciOiJSUz…”,
“expires_in” : 3600,

“state” : “9be638a9-0e05-42e1-b4f8-a3e30457fbdd”,
“token_type” : “Bearer”,}
При невозможности выдачи маркера доступа возвращается код ошибки 

Реализуем метод получения токена на java

public ResponseEntity<String> getToken(String code) throws Exception {
    logger.info("getToken start..");
    String timestamp = getEsiaDateNow();
    String state = UUID.randomUUID().toString();
    String clientMsg = scope + timestamp + clientId + state;
    byte[] signMessage = signerService.signMessageGost(clientMsg.getBytes());
    byte[] clientSecret = Base64.getEncoder().encode(signMessage);
    String grantPype = "authorization_code";

    HttpHeaders headers = new HttpHeaders();
    headers.set("Accept", MediaType.APPLICATION_JSON_VALUE);

    MultiValueMap<String, String> map = new LinkedMultiValueMap<String, String>();

    map.add("grant_type",       grantPype);
    map.add("timestamp",        timestamp);
    map.add("code",             code);
    map.add("redirect_uri",     tokenResult);
    map.add("client_id",        clientId);
    map.add("client_secret",    new String(clientSecret));
    map.add("state",            state);
    map.add("scope",            scope);
    logger.info("prepare get token HttpEntity..");
    HttpEntity<MultiValueMap<String, String>> entity =
            new HttpEntity<MultiValueMap<String, String>>(map, headers);


    ResponseEntity<String> exchange = httpCore.exchange(HttpMethod.POST, esiaSite + tokenUrl, entity);
    return exchange;
}

Предоставление персональных данных пользователей

Для получения персональных данных пользователей необходимо направить запрос методом GET к REST-API системы ЕСИА на соответствующий https-адрес.

В тестовой среде сервис доступен по URL:
https://esia-portal1.test.gosuslugi.ru/rs/prns

Структура ресурса в запросе

Иерархия идентификаторов ресурсов в ЕСИА выглядит следующим образом:
/prns/{oid}/{collection_name}/{collection_entity_id}

Обозначения:

  • prns — перечень (коллекция) пользователей, зарегистрированных в ЕСИА.

  • {oid} — внутренний идентификатор пользователя в ЕСИА.
    Получается:

    • из маркера идентификации в параметре sub или urn:esia:sbj:oid;

    • из маркера доступа в параметре urn:esia:sbj_id.

  • {collection_name} — ссылка на перечень (коллекцию) типов данных пользователя:

    • ctts — контактные данные;

    • addrs — адреса;

    • docs — документы пользователя;

    • orgs — организации, сотрудником которых является пользователь;

    • vhls — транспортные средства пользователя.

  • {collection_entity_id} — внутренний идентификатор элемента коллекции (например, контакта или документа).

Пример запроса

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

GET /rs/prns/6924 HTTP/1.1

Authorization: Bearer 75b2c7cbb8da403491c224c9e431cef9

Host: esia-portal1.test.gosuslugi.ru

Accept: */*

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

Пример реализации на Java

public Person getPrnsByToken(String token) {

    HttpEntity<String> entity = createEntity(token);

    logger.debug("getPrnsByToken..");

    ResponseEntity<String> exchange = httpCore.exchange(

        HttpMethod.GET, 

        esiaSite + personUrl + getUserIdByToken(token), 

        entity

    );

    return parsePersonFromPrns(exchange.getBody());

}

Заключение

Интеграция с ЕСИА предоставляет удобный и безопасный способ работы с государственными и коммерческими сервисами. Использование современных библиотек и OpenID Connect позволяет автоматизировать процессы и улучшить пользовательский опыт.

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

Если у вас возникли вопросы или вы хотите углубиться в тему — пишите комментарии.

Исходный код интеграции с ЕСИА

Если статья и исходники оказались полезными, не забудьте поставить ⭐️ на GitHub! ?

Комментарии (2)


  1. velon
    06.12.2024 06:27

    Я ожидал что сейчас будет о том как утекают данные с ЕСИА, а по сути: "Получаем персональные данные пользователей с Госуслуг если пользователи согласны их передавать"

    В общем что-то, мне кажется, не то с заголовком...

    И ещё вопрос: client_id - получается сначала надо как-то зарегистрировать свою систему в Госуслугах, абы кто не может на свой сайт закинуть авторизацию ЕСИА, пока эту процедуру не пройдёт (ещё и сертификат нужно получить)?

    И не описана работа с сертификатом, понятно что есть GitHub и можно в нём посмотреть signMessageGost, видимо, но для публикации это потеря: начали то с p12-сертификата, а потом это осталось без пояснений.

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


    1. aborouhin
      06.12.2024 06:27

      получается сначала надо как-то зарегистрировать свою систему в Госуслугах, абы кто не может на свой сайт закинуть авторизацию ЕСИА, пока эту процедуру не пройдёт (ещё и сертификат нужно получить)?

      По этому поводу тоже хотел сначала автору вопрос задать, как там оно сейчас на практике. Раньше вообще не пускали всех подряд к ЕСИА, только госорганы/кредитные/страховые/коммунальщиков/связистов/т.п. Сейчас по регламенту любой может свою ИС интегрировать, но есть много всяких "но" (аттестация на определённую категорию защиты перс. данных, сертификация средств криптографии, потом всё это рассматривается на комиссии...) - в общем, не вполне очевидно, насколько этот квест реально проходим.

      Но потом подумал, что вот этим всем в организации явно не бэкенд-разработчик занимался, и автору поста задавать эти вопросы бессмысленно :)