Что делать и как?
Сначала нам показалось, что в интеграции с ЕСИА нет ничего особенного с технической точки зрения — стандартная задача, связанная с получением данных посредством REST API. Однако, при ближайшем рассмотрении стало понятно, что не всё так просто. Например, выяснилось, что у нас нет представления о том, как работать с сертификатами, необходимыми для подписи нескольких параметров. Пришлось тратить время и разбираться. Но обо всем по порядку.
Для начала важно было наметить план действий. Наш план включал следующие основные шаги:
- зарегистрироваться на технологическом портале ЕСИА;
- подать заявки на использование программных интерфейсов ЕСИА в тестовой и промышленной среде;
- самостоятельно разработать механизм взаимодействия с ЕСИА (в соответствии с действующим документом «Методические рекомендации по использованию ЕСИА»);
- протестировать работу механизма в тестовой и промышленной среде ЕСИА.
Обычно мы разрабатываем наши проекты на 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 способа:
- oid содержится в jwt токене, расшифровав его
- После получения токена 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