Введение
Мы не будем описывать каждый шаг, который делали по документации, но расскажем о конкретном решении, которое нашли для получения данных по организациям пользователя.
В статье часто будет использоваться фраза «формировать запрос» — под ней мы понимаем формирование URL, по которому необходимо переадресовать пользователя. В этом URL зашифрованы все данные для ЕСИА, по которым она определяет, что это за запрос.
Также много внимания уделим составу scope. Scope — это область доступа, т. е. запрашиваемые права. Подробнее о формировании запросов можно прочесть в документации в пунктах B.2.1, B.2.2, B.2.3, B.2.4.
Описание
Компонент для авторизации на портале “Госуслуги”.
Opensslpkcs7 не может прочитать приватный ключ при авторизации через есиа госуслуги
Пытаюсь сделать авторизацию на сайте через ЕСИА. Для этого использую докер контейнер gost_php в котором установлен пакет https://github.com/ekapusta/oauth2-esia.
Клиент отправил свой сертификат безопасности и закрытый ключ в виде 6-ти файлов(header.key, masks.key, masks2.key, name.key, primary.key, primary2.key). С помощью скрипта https://github.com/garex/nodejs-gost-crypto я преобразовал закрытый ключ из 6-ти файлов в один в формате PEM.
Далее скопировал ключ из терминала в файл private.key(не только сам ключ, но и заголовки начала и конца ключа).
И пробросил сертификат и ключ в контейнер gost_php в папку certs.
Скрипт формирующий URI для обращения к API госуслуг.
<?php
use EkapustaOAuth2EsiaProviderEsiaProvider;
use EkapustaOAuth2EsiaSecurityJWTSignerOpenSslCliJwtSigner;
use EkapustaOAuth2EsiaSecuritySignerOpensslPkcs7;
require "vendor/autoload.php";
require "envReader.php";
header('Content-Type: application/json');
loadEnv();
$file = fopen(getcwd().'/esia.log', 'a ');
fwrite($file, "start.phpn");
$certPath = getenv('CERT_PATH');
$privateKeyPath = getenv('KEY_PATH');
$clientId = $_GET['client_id'];
$redirectUri = $_GET['redirect_uri'];
$remoteUrl = $_GET['remote_url'];
$provider = new EsiaProvider(
[
'clientId' => $clientId,
'redirectUri' => $redirectUri,
'defaultScopes' => ['birthdate', 'gender', 'email', 'mobile'],
'remoteUrl' => $remoteUrl,
'remotePublicKey' => EsiaProvider::RESOURCES.'esia.test.public.key',
'remoteCertificatePath' => EsiaProvider::RESOURCES.'esia.gost.prod.public.key',
],
[
'signer' => new OpensslPkcs7($certPath, $privateKeyPath, getenv('KEY_PASS')),
'remoteSigner' => new OpenSslCliJwtSigner('/usr/bin/openssl'),
]
);
try {
$link = $provider->getAuthorizationUrl();
fwrite($file, $link."n");
$_SESSION['oauth2.esia.state'] = $provider->getState();
fwrite($file, $_SESSION['oauth2.esia.state']."n");
$result = [
'link' => $link,
'state' => $provider->getState(),
];
$result = json_encode($result);
} catch(Exception $e) {
fwrite($file, $e->getMessage()."n");
fwrite($file, $e->getTraceAsString()."n");
$result = [
'error' => $e->getMessage()
];
$result = json_encode($result);
} finally {
fclose($file);
echo $result;
}
return;
К этому скрипту я обращаюсь из своего laravel приложения через guzzle:
public function buildAuthLink()
{
$client = new Client();
$response = $client->get($this->getLinkUrl, [
'query' => [
'client_id' => $this->appId,
'redirect_uri' => $this->redirectUri,
'remote_url' => $this->remoteUrl,
]
]);
$body = json_decode($response->getBody(), true);
dd($body);
$link = $body['link'];
$this->state = $body['state'];
return $link;
}
Но когда я пытаюсь продампить тело ответа вернувшегося через guzzle, я получаю exception:
array:1 [
"error" => "Can not read private key ./certs/private.key"
]
Почему пакет не может прочитать закрытый ключ ?
Внимание!
Получив токен вы можете выполнять любые API запросы. Библиотека не поддерживает все существующие методы в API, а предоставляет только самые базовые. Основная цель библиотеки – получение токена.
Выписка с данными в исходном виде:
Получение данных о документах в исходном виде происходит так же путем указания scope.Сервис доступен для типов документов VEHICLE_INFO (Выписка
о транспортном средстве по владельцу), ILS_PFR (Cведения о состоянии индивидуального
страхового счета застрахованного лица)
, PAYOUT_INCOME (Сведения о доходах
физического лица и о выплатах страховых взносов, произведенных в пользу физического
лица). Но в случае запроса данных ответ может поступить не сразу, а в течении некоторого времени.В случае, если сведение запрошено в ведомстве и ответ еще не поступил, возвращается
идентификатор этого запроса и идентификатор пользователя (oid).
С помощью идентификатора
запроса осуществляется процесс обработки персональных данных. Для возможности получения
сведения о выписки транспортого средства по владельцу необходимо обновить запрос. Перечень
параметров, которые возвращаются по каждому типу документа (doc_type), приведен в разделе
7.2 методических рекомендаций.
Пример ответа в случае запроса openid и fullname из защищённого хранилища ЕСИА:
{"sub":"3","info":{"uid":"1000486446","stateFacts":["EntityRoot"],"firstName":"Тимофей","lastName":"Сазонов","middleName":"Трофимович","trusted":true,"updatedOn":1633359785,"status":"REGISTERED","verifying":true,"rIdDoc":160710,"containsUpCfmCode":false,"eTag":"A543E45F09EDC6AEE19530A674E636B54F9A29CC"}}
Выход из системы есиа
Параметры:
Для получения данных отправляется GET запрос
Для инициации процесса выхода из системы ЕСИА необходимо сформировать авторизационный запрос и передать его на соответствующий URL шлюза. Для пользователя с вышеуказанными параметрами ссылка будет иметь следующий вид:
ВАЖНО! Путь возврата пользователя обязательно должен быть указан в соответствующей КИС
Доступ к сервисам шлюза
Для доступа к сервисам шлюза необходимо с помощью функциональных инструментов его административной панели создать КИС, указав в её параметрах «Пути возврата» – URL-адрес(а) клиентской ИС (потребителя идентификации), на которые шлюз будет отвечать после выполнения авторизации.
Для доступа к сервисам шлюза потребуются следующие реквизиты:
Значение идентификатора и секрета для КИС берутся из её параметров в административной панели шлюза.
ЕСИА Шлюз не занимается валидацией скоупов, переданных клиентской системой. Скоупы просто передаются в ЕСИА. Поэтому .well-known/openid-configuration содержит пустой массив в scopes_supported.
Задача и поиск решения
В рамках задачи нужно было авторизовать пользователя через Госуслуги и сразу же получить информацию по всем его организациям: ИНН, короткое название и полное название.
Авторизовать пользователя через ЕСИА не составило труда, но вот с получением информации о его организациях вышла совсем другая история.
Неочевидным был момент о том, что сначала мы должны получить данные только о пользователе и максимум — список ID его организаций, а затем делать отдельный запрос на каждую организацию.
Изначально мы пытались получить данные по пользователю и его организациям в одном запросе, указав все поля в scope, но это не работало. Как же получить данные по организациям?
Зачем нам есиа?
Согласно Федеральному закону № 225-ФЗ от 28.06.2021 «О внесении изменений в часть первую Гражданского кодекса Российской Федерации», многие организаций в РФ получили право проводить официальные собрания и голосования по корпоративным вопросам дистанционно.
Ранее решения с юридической силой требовали очных собраний или голосований по почте. Голосования по почте не отличаются надежностью, а собрать много руководителей со всей России в одном месте — это кошмар с точки зрения затрат.
Чтобы проводить мероприятия принятия решения дистанционно в соответствии с новым федеральным законом, необходимо предоставить возможность достоверного установления личности участников. В России это возможно через проверку доступа к верифицированному аккаунту на Госуслугах.
Как использовать
Пример получения ссылки для авторизации
После редиректа на ваш redirectUrl вы получите в $_GET[‘code’] код для получения токена
Пример получения токена и информации о пользователе
Как получить oid?
Если 2 способа:
- oid содержится в jwt токене, расшифровав его
- После получения токена oid сохраняется в config и получить можно так
Конфиг
clientId – ID вашего приложения.
redirectUrl – URL куда будет перенаправлен ответ с кодом.
Модуль авторизации есиа
Модуль представляет собой набор файлов c исходным кодом. Имеются модификации на языках программирования PHP, C# (.Net), Java, Ruby, Python, а также готовые компоненты для CMS Bitrix (1С-Битрикс «Управление сайтом» и «Корпоративный портал»), WordPress, Drupal и Joomla и Moodle.
При приобретении модуля мы оказываем техническую и консультационную поддержку до успешного завершения интеграции!
Поможем решить организационно-бюрократические вопросы, оформим заявки для Минкомсвязи Р Ф, проконсультируем ваших технических специалистов по возникающим в процессе интеграции вопросам. Предоставим возможность отладить решение в официальной тестовой среде ЕСИА.
Благодаря наличию готового программного кода и квалифицированной технической поддержке модуль успешно интегрирован более чем в 200 информационных систем по всей России, среди которых порталы федеральных органов исполнительной власти, сайты региональных и муниципальных уровней, а также информационные системы крупных финансовых и страховых компаний, банков, операторов связи и медицинских организаций.
Модуль поддерживает биометрическую идентификацию и соответствует требованиями Методических рекомендаций единой биометрической системы (ЕБС) и Методическим рекомендациям по использованию Единой системы идентификации и аутентификации.
Общие положения
Взаимодействие клиентской системы с сервисами шлюза, обеспечивающими идентификацию пользователей через ЕСИА, происходит через стандартный протокол OAuth 2.0/OpenID Connect. Авторизация в ЕСИА происходит, в данном случае, как обычная OAuth авторизация.
Для работы с данным протоколом имеются готовые открытые реализации под большинство популярных программных платформ.
Чтобы начать работу с ЕСИА Шлюзом необходимо интегрировать в целевую информационную систему одно из готовых средств, реализующих работу с протоколом OAuth 2.0.
Спецификация OpenID Connect
Переиспользование токена
Дополнительно укажите токен и идентификатор в конфиге
Получение данных о пользователе
Идентификационный токен пользователя необходимо проверить с помощью публичного RSA ключа от ЕСИА и получить из него id пользователя. С помощью этого id и accessToken, который мы получили в предыдущем шаге, мы уже наконец можем запросить персональные данные пользователя.
Получение данных пользователя
Параметры:
- access_token – токен доступа.
Для получения данных отправляется GET запрос
на url при этом в параметре запроса access_token передается токен доступа.
Пример запроса на получение данных:
На основе полученного токена доступа шлюз предоставляет клиентским системам доступ к областям данных из защищённого хранилища ЕСИА.
ВАЖНО! В соответствии с законодательством перечень скоупов доступных для клиентских систем шлюза может быть ограничен. Поэтому, прежде чем указывать перечень скоупов в запросе к шлюзу, необходимо сверить этот перечень с перечнем скоупов указанным в заявке на подключение к тестовой/промышленной ЕСИА.
Полный перечень скоупов и соответствующих им данных, которые могут быть запрошены из защищённого хранилища ЕСИА можно найти в методических рекомендациях по использованию ЕСИА.
Получение документов из цифрового профиля:
Запрос документов происходит так же, как и запрос других данных – путем указания нужного scope. Но в случае запроса документа ответ может поступить не сразу, а в течении некоторого времени.В случае, если сведение запрошено в ведомстве и ответ еще не поступил, возвращается
идентификатор этого запроса и идентификатор пользователя (oid).
С помощью идентификатора
запроса осуществляется процесс обработки персональных данных. Для возможности получения
сведения о документах необходимо обновить запрос. Для типа документа
INCOME_REFERENCE (Справка о доходах и суммах налога физического лица (форма 2-НДФЛ))
дополнительно указывается параметр year – год, за который успешно получена справка 2-НДФЛ
в ведомстве.
В случае, если у пользователя нет информации по следющим сведениям: FID_BRTH_CERT,
OLD_BRTH_CERT, RF_BRTH_CERT, MARRIED_CERT, DIVORCE_CERT,
NAME_CHANGE_CERT и FATHERHOOD_CERT, то в ответе на запрос вернется ошибка 404.
Перечень параметров, которые возвращаются по каждому типу документа (doc_type),
приведены в разделе 7.2 методических рекомендаций.
Решение проблемы: авторизация через госуслуги (php, oauth, esia) /
Везде идет речь о сертификате (1 файл), о закрытом ключе (1 файл) и пароле к закрытому ключу (строка)
Для обмена с ЕСИА потребуется шифрование, поэтому у вас должен быть свой RSA-ключ для подписи. Закрытый ключ находится у вас, публичный сертификат выгружается в кабинет ЕСИА.
Некие сертификаты, общедоступные по ссылке http://esia.gosuslugi.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://github.com/ekapusta/oauth2-esia
Описание работы с ним https://vhod-v-lichnyj-kabinet.ru/ru/post/358834/
Кстати, если посмотрите тесты на гитхабе, то найдете там мнемонику для тестовой организации и готовые ключи для нее. Можно будет протестировать.
Создание ссылки для редиректа на страницу есиа
Все начинается с того, что пользователь решает пройти авторизацию через ЕСИА. Создаем на бэкенде ссылку для перехода с использованием нашего инструмента формирования подписей.
async getAuthLink(redirectLink: string) {
const params = await this.signParams({
redirect_uri: redirectLink,
response_type: 'code',
access_type: 'offline',
})
const authQuery = new URLSearchParams(params)
const authURL = `${this.esiaHost}/aas/oauth2/ac`
return `${authURL}?${authQuery}`
}
В redirectLink необходимо указать адрес страницы, на которую ЕСИА перенаправит пользователя после успешной аутентификации. Созданную ссылку возвращаем на фронтенд и перенаправляем на нее пользователя.
Стек и схема интеграции
Для интеграции мы используем:
Токен и oid
Токен – jwt токен которые вы получаете от ЕСИА для дальнейшего взаимодействия
oid – уникальный идентификатор владельца токена
Установка
При помощи composer:
Или добавьте в composer.json
Формирование подписи
Прежде чем разбирать все по порядку, кое о чем стоит подумать заранее. В отличие от других интеграций, запросы к ЕСИА должны сопровождаться подписью ГОСТ Р 34.10/11-2022, а не просто API key. Создать такую подпись можно с помощью утилиты КриптоПро CSP.
Для нас основная задача здесь — правильно обернуть эту утилиту в Docker, чтобы с ней можно было работать как с отдельным сервисом в рамках нашей инфраструктуры. Получившийся сервис мы выложили в открытый доступ на гитхабе. Инструкция по запуску есть в README.md.
В процесс сборки Docker образа сервиса с утилитой КриптоПро мы встроили:
Таким образом вся криптография собрана в отдельном самостоятельном компоненте, который можно использовать, когда необходимо что-нибудь подписать. Вот как это выглядит на бэкенде:
private async signParams(params: Record<string, string>) {
const time = moment().format('YYYY.MM.DD HH:mm:ss ZZ')
const state = uuid()
const clientId = this.clientId
const scope = this.scope
const { data: { result: clientSecret } } = await axios.post<{ result: string }>(
`${this.cryptoProServiceAddress}/cryptopro/sign`,
{ text: [scope, time, clientId, state].join('') },
)
return {
...params,
timestamp: time,
client_id: clientId,
scope,
state,
client_secret: clientSecret.replace(/n/g, ''),
}
}
С созданием подписей разобрались, теперь последовательно разберем, как реализовать схему выше.