Интеграция с ЕСИА на базе oauth2-client (PHP) / Хабр

Что делать и как?

Сначала нам показалось, что в интеграции с ЕСИА нет ничего особенного с технической точки зрения — стандартная задача, связанная с получением данных посредством REST API. Однако, при ближайшем рассмотрении стало понятно, что не всё так просто. Например, выяснилось, что у нас нет представления о том, как работать с сертификатами, необходимыми для подписи нескольких параметров. Пришлось тратить время и разбираться. Но обо всем по порядку.

Для начала важно было наметить план действий. Наш план включал следующие основные шаги:

  1. зарегистрироваться на технологическом портале ЕСИА;
  2. подать заявки на использование программных интерфейсов ЕСИА в тестовой и промышленной среде;
  3. самостоятельно разработать механизм взаимодействия с ЕСИА (в соответствии с действующим документом «Методические рекомендации по использованию ЕСИА»);
  4. протестировать работу механизма в тестовой и промышленной среде ЕСИА.

Обычно мы разрабатываем наши проекты на Java. Поэтому для программной реализации выбрали:

Описание

Компонент для авторизации на портале “Госуслуги”.

Что теперь делать со всеми этими данными?

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

Пример получения объекта с необходимыми полями:

final ObjectMapper objectMapper = new ObjectMapper()
	.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

String personDataEntityString = esiaPersonDataFetcher
	.apply(ESIA_REST_API_URL   "/prns/"   esiaAccountId);

EsiaPersonDto esiaPersonDto = objectMapper
	.readValue(personDataEntityString, EsiaPersonDto.class);


Заполняем объект esiaPersonDto необходимыми данными, например, контактами:

for (String contactUrl : esiaListDto.getElementUrls()) {
  String contactEntityString = esiaPersonDataFetcher.apply(contactUrl);
  EsiaContactDto esiaContactDto = objectMapper.readValue(contactEntityString, EsiaContactDto.class); // Десериализация контакта
  if (esiaContactDto.getType() == null) continue;
  switch (esiaContactDto.getType().toUpperCase()) {
    case EsiaContactDto.MBT: // Если это номер мобильного телефона, то заполним поле mobilePhone
      esiaPersonDto.setMobilePhone(esiaContactDto.getValue());
      break;
    case EsiaContactDto.EML: // Если это адрес электронной почты, то заполним поле email
      esiaPersonDto.setEmail(esiaContactDto.getValue());
  }
}

Класс EsiaPersonDto выглядит следующим образом:

@Data
@FieldNameConstants(prefix = "")
public class EsiaPersonDto {

  private String firstName;
  private String lastName;
  private String middleName;
  private String birthDate;
  private String birthPlace;
  private Boolean trusted;  // тип учетной записи - подтверждена (“true”) / не подтверждена (“false”)
  private String status;    // статус УЗ - Registered (зарегистрирована) /Deleted (удалена)
  // Назначение полей непонятно, но они есть при запросе /prns/{oid}
  private List<String> stateFacts;
  private String citizenship;
  private Long updatedOn;
  private Boolean verifying;
  @JsonProperty("rIdDoc")
  private Integer documentId;
  private Boolean containsUpCfmCode;
  @JsonProperty("eTag")
  private String tag;
  // ----------------------------------------
  private String mobilePhone;
  private String email;

  @javax.validation.constraints.Pattern(regexp = "(\d{2})\s(\d{2})")
  private String docSerial;

  @javax.validation.constraints.Pattern(regexp = "(\d{6})")
  private String docNumber;

  private String docIssueDate;

  @javax.validation.constraints.Pattern(regexp = "([0-9]{3})\-([0-9]{3})")
  private String docDepartmentCode;

  private String docDepartment;

  @javax.validation.constraints.Pattern(regexp = "\d{14}")
  @JsonProperty("snils")
  private String pensionFundCertificateNumber;

  @javax.validation.constraints.Pattern(regexp = "\d{12}")
  @JsonProperty("inn")
  private String taxPayerNumber;

  @JsonIgnore
  @javax.validation.constraints.Pattern(regexp = "\d{2}")
  private String taxPayerCertificateSeries;

  @JsonIgnore
  @javax.validation.constraints.Pattern(regexp = "\d{10}")
  private String taxPayerCertificateNumber;
}

Работа по усовершенствованию сервиса будет продолжаться, ведь ЕСИА не стоит на месте.

Авторизация через госуслуги

Везде идет речь о сертификате (1 файл), о закрытом ключе (1 файл) и пароле к закрытому ключу (строка)

Для обмена с ЕСИА потребуется шифрование, поэтому у вас должен быть свой RSA-ключ для подписи. Закрытый ключ находится у вас, публичный сертификат выгружается в кабинет ЕСИА.

Некие сертификаты, общедоступные по ссылке http://vhod-v-lichnyj-kabinet.ru/public/esia.zip

Поскольку ЕСИА тоже будет присылать вам шифрованную информацию, то вам потребуется публичный сертификат ЕСИА, чтобы расшифровать эту информацию.

Файлы от заказчика содержат один файл с расширением cer

Скорей всего – это публичный сертификат. Можете попробовать его открыть. В windows должно открыться стандартное окно с информацией о сертификате.

и папку с несколькими файлами с расширением key (header.key, masks.key, primary.key и тд)

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

Для авторизации через госуслуги можно использовать, например, самоподписанные сертификаты:

Сначала создать закрытый ключ и запрос на сертификат

openssl req -newkey rsa:2048 -nodes -keyout domain.key -out domain.csr -sha256

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

Потом выпустить публичный сертификат для него

openssl x509 -signkey domain.key -in domain.csr -req -days 1825 -out domain.crt -sha256

у вас будет два файла domain.key (закрытый ключ) и domain.crt (сертификат). Сертификат потом надо будет выгрузить в кабинет ЕСИА.

Как мне эти данные использовать?

Очень помог в интеграции с ЕСИА пакет https://vhod-v-lichnyj-kabinet.ru/ekapusta/oauth2-esia
Описание работы с ним https://vhod-v-lichnyj-kabinet.ru/ru/post/358834/

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

Аутентификация через есиа. есть реализация на c#?

Добрый день, ребята!

Есть ли у кого-то последовательный алгоритм аутентификации через ЕСИА?
Особая благодарность за код на C#!

Благодарим за внимание

Пакет заоперсорсен финтех-компанией в которой я работаю. Не тестировалось на животных.

Внимание!

Получив токен вы можете выполнять любые API запросы. Библиотека не поддерживает все существующие методы в API, а предоставляет только самые базовые. Основная цель библиотеки – получение токена.

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

В связи с пандемией коронавируса количество офлайн сделок во многих направлениях кредитования начало сокращаться. Клиенты стали «уходить в онлайн», и для нас было жизненно важно укрепить своё онлайн-присутствие на рынке автокредитования. В процессе доработки сервиса «Автокредит» (на Хабре уже есть

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

Как обновить токен?

Стандартно, как описано в документации к oauth2-client

Как получить oid?

Если 2 способа:

  1. oid содержится в jwt токене, расшифровав его
  2. После получения токена oid сохраняется в config и получить можно так

Конфиг

clientId – ID вашего приложения.

redirectUrl – URL куда будет перенаправлен ответ с кодом.

Переиспользование токена

Дополнительно укажите токен и идентификатор в конфиге

Позволяет:

  • Сформировать ссылку для перехода на сайт ЕСИА с целью авторизации
  • Завершает процедуру авторизации обменивая временный код на access token
  • Опционально может производить JWT (JSON Web Token) валидацию ответа ЕСИА (при наличии публичного ключа ЕСИА)
  • Для формирования открепленной подписи запросов, в качестве бэкенда может использоваться
    модуль M2Crypto или openssl через системный вызов (указывается в настройках)
  • Выполнять информационные запросы к ЕСИА REST сервису для получения сведений о персоне:
    • Основаная информация
    • Адреса
    • Контактная информация
    • Документы
    • Дети
    • Транспортные средства

Получаем данные пользователя

Проверяя стейт и меняя код на аутентификационный токен.

Получение url для переадресации

Первый шаг ― это получение авторизационного кода. В нашем случае это делает отдельный сервис с переадресацией на страницу авторизации портала Госуслуг (расскажем об этом немного подробнее).

Сначала мы инициализируем переменные ESIA_AUTH_URL (адрес ЕСИА) и API_URL (адрес, на который происходит редирект в случае успешной авторизации). После этого создаем объект EsiaRequestParams, который содержит в своих полях параметры запроса к ЕСИА, и сформируем ссылку esiaAuthUri.

public Response loginByEsia() throws Exception {
  final String ESIA_AUTH_URL = dao.getEsiaAuthUrl(); // Адрес ЕСИА
  final String API_URL = dao.getApiUrl(); // Адрес, на который произойдет редирект с случае успешной авторизации
  EsiaRequestParams requestDto = new EsiaRequestParams(API_URL);
  URI esiaAuthUri = new URIBuilder(ESIA_AUTH_URL)
          .addParameters(Arrays.asList(
            new BasicNameValuePair(RequestEnum.CLIENT_ID.getParam(), requestDto.getClientId()),
            new BasicNameValuePair(RequestEnum.SCOPE.getParam(), requestDto.getScope()),
            new BasicNameValuePair(RequestEnum.RESPONSE_TYPE.getParam(), requestDto.getResponseType()),
            new BasicNameValuePair(RequestEnum.STATE.getParam(), requestDto.getState()),
            new BasicNameValuePair(RequestEnum.TIMESTAMP.getParam(), requestDto.getTimestamp()),
            new BasicNameValuePair(RequestEnum.ACCESS_TYPE.getParam(), requestDto.getAccessType()),
            new BasicNameValuePair(RequestEnum.REDIRECT_URI.getParam(), requestDto.getRedirectUri()),
            new BasicNameValuePair(RequestEnum.CLIENT_SECRET.getParam(), requestDto.getClientSecret())
          ))
          .build();
  return Response.temporaryRedirect(esiaAuthUri).build();
}

Для наглядности покажем, как может выглядеть класс EsiaRequestParams:

public class EsiaRequestParams {

  String clientId;
  String scope;
  String responseType;
  String state;
  String timestamp;
  String accessType;
  String redirectUri;
  String clientSecret;
  String code;
  String error;
  String grantType;
  String tokenType;

  public EsiaRequestParams(String apiUrl) throws Exception {
    this.clientId = CLIENT_ID;
    this.scope = Arrays.stream(ScopeEnum.values())
            .map(ScopeEnum::getName)
            .collect(Collectors.joining(" "));
    responseType = RESPONSE_TYPE;
    state = EsiaUtil.getState();
    timestamp = EsiaUtil.getUrlTimestamp();
    accessType = ACCESS_TYPE;
    redirectUri = apiUrl   RESOURCE_URL   "/"   AUTH_REQUEST_ESIA;
    clientSecret = EsiaUtil.generateClientSecret(String.join("", scope, timestamp, clientId, state));
    grantType = GRANT_TYPE;
    tokenType = TOKEN_TYPE;
  }
}

Получение данных пользователя


В нашем случае необходимо получить ФИО, дату рождения, паспортные данные и контакты.

Используем функциональный интерфейс, который поможет получать данные пользователя:

Получение токена

Следующий шаг ― получение маркера доступа (он же токен) в обмен на авторизационный код, который был получен в качестве параметра при успешной авторизации пользователя на портале Госуслуг.

Для получения каких-либо данных в ЕСИА нужно получить токен доступа. Для этого формируем запрос в ЕСИА. Основные поля запроса тут формируются аналогичным образом, в коде получается примерно следующее:

Предварительные условия

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

Пример использования в django

Создайте конфигурационный файл esia.ini следующего содержания:

[esia]
### Внимание! Все пути указываются относительно данного файла.
# Базовый адрес сервиса ЕСИА, в данном случае указана тестовая среда
SERVICE_URL: https://esia-portal1.test.gosuslugi.ru

# Идентификатор информационной системы, указывается в заявке на подключение
CLIENT_ID: MYIS01

# Публичный ключ/сертфикат (необязателен, используется только для m2crypto или openssl)
CERT_FILE: keys/my_public_cert.crt

# Приватный ключ (необязателен, используется только для m2crypto или openssl)
PRIV_KEY_FILE: keys/my_private.key

# Публичный ключ сервиса ЕСИА, для валидации ответов (необязателен)
JWT_CHECK_KEY: keys/esia_test_pub.key

# Адрес страницы, на которую будет перенаправлен браузер после авторизации в ЕСИА
REDIRECT_URI: http://127.0.0.1:8000/esia/callback/

# Адрес страницы, на которую необходимо перенаправить браузер после логаута в ЕСИА (опционально)
LOGOUT_REDIRECT_URI: http://127.0.0.1:8000

# Список scope через пробел. Указывается в заявке, openid при авторизации обязателен
SCOPE: openid http://esia.gosuslugi.ru/usr_inf

# Используемый крипто бэкенд: m2crypto, openssl (системный вызов)
# или csp (системный вызов утилиты cryptcp из состава КриптоПРО CSP)
CRYPTO_BACKEND: m2crypto

# SHA1 отпечаток сертификата связанного с закрытым ключем, смотреть по выводу certmgr --list
# (необязателен, используется только для csp)
CSP_CERT_THUMBPRINT: 5c84a6a58bbeb6578ff7d26f4ea65b6de5f9f5b8

# Пароль (пин-код) контейнера с закрктым ключем
# (необязателен, используется только для csp)
CSP_CONTAINER_PWD: 12345678

В свой urls.py добавьте:

В свой views.py добавьте:

importjsonfromdjango.httpimportHttpResponseRedirect, HttpResponsefromdjango.contrib.auth.viewsimportlogoutfromesia.clientimportEsiaConfig, EsiaAuthESIA_SETTINGS=EsiaConfig('/full/path/to/esia.ini')

defesia_login(request):
    esia_auth=EsiaAuth(ESIA_SETTINGS)
    esia_login_url=esia_auth.get_auth_url()
    returnHttpResponseRedirect(esia_login_url)

defesia_logout(request):
    kwargs= {}
    esia_auth=EsiaAuth(ESIA_SETTINGS)
    kwargs['next_page'] =esia_auth.get_logout_url()
    returnlogout(request, **kwargs)

defesia_callback(request):
    esia_auth=EsiaAuth(ESIA_SETTINGS)
    ifrequest.GET.has_key('error'):
        data= {
            'error': request.GET['error'],
            'error_description': request.GET['error_description'],
        }
    else:
        data= []
        code=request.GET['code']
        state=request.GET['state']
        esia_client=esia_auth.complete_authorization(code, state)
        # Для отключения JWT валидации ответов ЕСИА, можно так:# esia_client = esia_auth.complete_authorization(code, state, validate_token=False)# Запрос информации о персонеmain_info=esia_client.get_person_main_info()
        pers_doc=esia_client.get_person_documents()
        pars_addr=esia_client.get_person_addresses()
        pers_contacts=esia_client.get_person_contacts()
        pers_kids=esia_client.get_person_kids()
        pers_trans=esia_client.get_person_transport()

        data.append(main_info)
        data.append(pers_doc)
        data.append(pars_addr)
        data.append(pers_contacts)
        data.append(pers_kids)
        data.append(pers_trans)

    # Просто выводим информацию. Здесь далее должна идти внутренняя логика авторизации# вашей информационной системы.returnHttpResponse(json.dumps(data, cls=json.JSONEncoder, ensure_ascii=False, indent=4),
        content_type='application/json')

Редиректим посетителя на есиа

Одновременно сохраняя стейт для последующей проверки.

Токен и oid

Токен – jwt токен которые вы получаете от ЕСИА для дальнейшего взаимодействия

oid – уникальный идентификатор владельца токена

Установка

При помощи composer:

Или добавьте в composer.json

Похожее:  Учитель Будущего — личный кабинет: вход на курсы повышения

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *