fr05t1k/esia

OpenID ESIA authenticating

Maintainers

Package info

github.com/fr05t1k/esia

pkg:composer/fr05t1k/esia

Transparency log

Statistics

Installs: 106 482

Dependents: 4

Suggesters: 0

Stars: 145

Open Issues: 0


README

PHP-компонент для авторизации через Единую систему идентификации и аутентификации (ЕСИА) портала «Госуслуги».

CI Latest Version Total Downloads PHP Version License

⚠️ Обновляйтесь на свой страх и риск. У авторов и контрибьюторов не всегда есть доступ к реальной среде ЕСИА: и промышленный контур, и тестовый стенд esia-portal1.test.gosuslugi.ru доступны только из РФ и требуют зарегистрированной ИС и ГОСТ-сертификата. Поэтому изменения проверяются офлайн и против реального ЕСИА могут быть не протестированы. Если обновление заработало (или нет) с реальной средой, пожалуйста, оставьте короткий отчёт в обсуждении: Отчёты о совместимости с реальной средой ЕСИА.

Основная цель библиотеки — получение токена ЕСИА. Получив токен, вы можете выполнять запросы к API; библиотека предоставляет самые базовые методы, а не все существующие в API ЕСИА.

Оглавление

Требования

PHP >= 8.3, расширения ext-openssl и ext-json.

Библиотека не привязана к конкретному HTTP-клиенту и использует стандарты PSR-18 (HTTP Client) и PSR-17 (HTTP Factories). Необходимо установить любую их реализацию, например Guzzle:

composer require guzzlehttp/guzzle

Подойдёт любой PSR-18 клиент (Guzzle, Symfony HTTP Client, и т.д.) вместе с PSR-17 фабрикой (nyholm/psr7, guzzlehttp/psr7 и т.п.). Реализация находится автоматически (через php-http/discovery), либо её можно передать явно в конструктор OpenId:

$esia = new \Esia\OpenId($config, $psr18Client, $psr17RequestFactory, $psr17StreamFactory);

Установка

При помощи composer:

composer require --prefer-dist fr05t1k/esia

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

"fr05t1k/esia" : "^3.0"

Быстрый старт

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

<?php 
$config = new \Esia\Config([
  'clientId' => 'INSP03211',
  'redirectUrl' => 'http://my-site.com/response.php',
  'portalUrl' => 'https://esia-portal1.test.gosuslugi.ru/',
  'scope' => ['fullname', 'birthdate'],
]);
$esia = new \Esia\OpenId($config);
$esia->setSigner(new \Esia\Signer\SignerPKCS7(
    'my-site.com.pem',
    'my-site.com.pem',
    'password',
    '/tmp'
));
?>

<a href="<?=$esia->buildUrl()?>">Войти через портал госуслуги</a>

После редиректа на ваш redirectUrl вы получите в $_GET['code'] код для получения токена

Пример получения токена и информации о пользователе

$esia = new \Esia\OpenId($config);

// Вы можете использовать токен в дальнейшем вместе с oid 
$token = $esia->getToken($_GET['code']);

$personInfo = $esia->getPersonInfo();
$addressInfo = $esia->getAddressInfo();
$contactInfo = $esia->getContactInfo();
$documentInfo = $esia->getDocInfo();

// Организации и роли пользователя (нужен полученный токен):
$roles = $esia->getRoles();               // членство в организациях + роли (одним запросом)
$organizations = $esia->getOrganizations(); // подробные данные по каждой организации

Организации и роли

Метод getRoles() возвращает список организаций, в которых состоит пользователь, вместе с ролями (эндпоинт rs/prns/{oid}/roles). Данные приходят одним запросом — каждый элемент содержит oid организации, краткое/полное наименование, ОГРН, флаги chief/admin и т.д. Если пользователь не состоит ни в одной организации, возвращается пустой массив.

Метод getOrganizations() использует коллекцию rs/prns/{oid}/orgs, элементы которой — ссылки на ресурсы организаций; каждая ссылка догружается и возвращается полными данными организации.

// Токен уже получен через getToken()
$roles = $esia->getRoles();
foreach ($roles as $role) {
    echo $role['shortName'], PHP_EOL;
}

$organizations = $esia->getOrganizations();

OAuth state (защита от CSRF)

state — одноразовый идентификатор запроса авторизации. Библиотека генерирует его автоматически при вызове buildUrl()/getToken() и сохраняет в Config, поэтому его можно получить и сверить при возврате пользователя:

$config = new \Esia\Config([/* ... */]);
$esia = new \Esia\OpenId($config);

$url = $esia->buildUrl();
$state = $config->getState(); // сохраните в сессии

// ... после редиректа сверьте $_GET['state'] с сохранённым значением

Свой state можно передать и напрямую в buildUrl($state) — удобно, когда нужно различать несколько одновременно открытых окон входа. Переданное значение попадёт в URL и сохранится в Config:

$url = $esia->buildUrl($myState);

Можно задать свой state — через параметр конфигурации или setState() — тогда он будет использован вместо сгенерированного:

$config = new \Esia\Config([/* ... */, 'state' => $myState]);
// или
$config->setState($myState);

Конфигурация

Параметр По умолчанию Описание
clientId ID вашего приложения.
redirectUrl URL, куда будет перенаправлен ответ с кодом.
portalUrl https://esia-portal1.test.gosuslugi.ru/ Домен портала для авторизации (только домен), по умолчанию — тестовая среда. Для продуктивной среды укажите https://esia.gosuslugi.ru/.
codeUrlPath aas/oauth2/v2/ac URL для получения кода авторизации. Прежний путь aas/oauth2/ac выведен из эксплуатации; при необходимости старое значение можно вернуть через этот параметр.
tokenUrlPath aas/oauth2/v3/te URL для получения токена. Прежний путь aas/oauth2/te выведен из эксплуатации; при необходимости старое значение можно вернуть через этот параметр.
clientCertificateHash пусто Хэш (fingerprint) сертификата системы-клиента в hex-формате. Обязателен для актуального эндпоинта получения токена aas/oauth2/v3/te (иначе ЕСИА вернёт ESIA-007014). Сертификат должен быть предварительно зарегистрирован в ЕСИА и привязан к УЗ системы-клиента. Значение — отпечаток сертификата по SHA-256 в hex (например, openssl x509 -in cert.pem -noout -fingerprint -sha256); точный способ вычисления см. в методических рекомендациях ЕСИА. Если не задан, не отправляется (обратная совместимость).
scope fullname birthdate gender email mobile id_doc snils inn Запрашиваемые права у пользователя.
privateKeyPath Путь до приватного ключа.
privateKeyPassword Пароль от приватного ключа.
certPath Путь до сертификата.
tmpPath Путь до директории, где будет проходить подпись (должна быть доступна для записи).
state пусто OAuth-идентификатор запроса (анти-CSRF). Если не задан, генерируется автоматически при buildUrl()/getToken() и сохраняется в Config (доступен через getState()). Можно задать свой — он будет использован вместо сгенерированного.
esiaCertPath пусто Путь до сертификата подписи ЕСИА (для продуктивной среды — GOST-2012). Если задан, полученный JWT автоматически проверяется: подпись, exp/nbf/iat, iss и аудитория (aud/client_id). Если не задан, проверка пропускается (обратная совместимость).
esiaTokenIssuer пусто Ожидаемое значение claim iss в токене. Если не задано, iss не проверяется.
tokenLeeway 60 Допустимое отклонение (в секундах) при проверке временных claim'ов (exp, nbf, iat) для компенсации рассинхронизации часов.

Проверка JWT (подпись и claim'ы)

По умолчанию библиотека не проверяет подпись полученного от ЕСИА токена — это сохраняет обратную совместимость. Чтобы включить проверку, укажите путь до сертификата подписи ЕСИА через esiaCertPath (и, при необходимости, esiaTokenIssuer):

$config = new \Esia\Config([
    // ... остальные параметры
    'esiaCertPath'    => '/path/to/esia-signing-cert.pem',
    'esiaTokenIssuer' => 'http://esia.gosuslugi.ru/',
]);
$esia = new \Esia\OpenId($config);

// getToken выбросит наследника InvalidTokenException при некорректном токене:
// SignatureInvalidException — неверная подпись,
// TokenExpiredException     — истёк срок (exp) или ещё не действителен (nbf),
// InvalidClaimException     — неверный iss / аудитория (aud/client_id).
$token = $esia->getToken($_GET['code']);

Проверка сделана подключаемой (pluggable): вы можете передать собственную реализацию \Esia\Token\TokenValidatorInterface (например, с проверкой GOST-подписи через CryptoPro) через \Esia\OpenId::setTokenValidator(). Стандартная реализация \Esia\Token\JwtValidator проверяет подпись через \Esia\Token\OpenSslSignatureVerifier (RSA из коробки; алгоритмы GOST-2012 — при наличии GOST-движка в OpenSSL).

Подпись запросов (сигнеры)

Для получения токена запрос к ЕСИА должен быть подписан. ЕСИА требует подпись ГОСТ Р 34.10-2012 (RSA больше не поддерживается в продуктивной среде). Сигнер задаётся через \Esia\OpenId::setSigner() и должен реализовывать \Esia\Signer\SignerInterface. Доступны следующие реализации:

Сигнер Механизм Требования
\Esia\Signer\SignerPKCS7 Нативный openssl_pkcs7_sign() Стандартный PHP с ext-openssl. Не умеет ГОСТ в обычной сборке OpenSSL — подходит только для тестов/RSA.
\Esia\Signer\CliSignerPKCS7 Вызов openssl smime -engine gost OpenSSL, собранный с GOST-движком (libengine-gost-openssl1.1), в PATH. Ключ и сертификат ГОСТ в PEM.
\Esia\Signer\CliCryptoProSigner Вызов утилиты csptest Установленный КриптоПро CSP с утилитой csptest. Ключ ГОСТ в контейнере CSP, указывается по имени контейнера.
\Esia\Signer\CryptoProSigner PHP-расширение КриптоПро (\CPRawSignature) Проприетарное PHP-расширение КриптоПро. Сертификат ГОСТ в хранилище My текущего пользователя.

Пример с CLI-сигнером ГОСТ (OpenSSL + GOST-движок):

$esia->setSigner(new \Esia\Signer\CliSignerPKCS7(
    '/path/to/gost-cert.pem',
    '/path/to/gost-key.pem',
    'key-password',
    '/tmp'
));

Пример с КриптоПро через утилиту csptest (подпись формируется по методическим рекомендациям ЕСИА: «сырое» значение подписи разворачивается побайтово и кодируется в base64 url-safe):

$esia->setSigner(new \Esia\Signer\CliCryptoProSigner(
    'my-container',  // имя контейнера ключа в CSP
    'password',      // пароль контейнера (если задан)
    'csptest',       // путь до csptest (по умолчанию 'csptest')
    '/tmp'           // каталог для временных файлов (по умолчанию системный)
));

Пример с КриптоПро через PHP-расширение:

$esia->setSigner(new \Esia\Signer\CryptoProSigner(
    '745187e5c161cd2e3130d886f9df4492fa270685', // отпечаток сертификата
    'pin' // PIN контейнера (если задан)
));

Все сигнеры поддерживают PSR-3 логгер через setLogger().

Сигнер и логгер также можно передать сразу в конструктор \Esia\OpenId (удобно для DI-контейнеров), не прибегая к сеттерам:

$esia = new \Esia\OpenId(
    $config,
    $httpClient,      // ?Psr\Http\Client\ClientInterface
    $requestFactory,  // ?Psr\Http\Message\RequestFactoryInterface
    $streamFactory,   // ?Psr\Http\Message\StreamFactoryInterface
    $signer,          // ?Esia\Signer\SignerInterface
    $logger           // ?Psr\Log\LoggerInterface
);

Любой из аргументов можно передать как null — тогда используется значение по умолчанию. Методы setSigner() и setLogger() продолжают работать.

Работа с токеном и oid

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

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

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

Есть 2 способа:

  1. oid содержится в JWT-токене — расшифровав его;
  2. после получения токена oid сохраняется в Config и доступен так:
$esia->getConfig()->getOid();

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

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

$config->setToken($jwt);
$config->setOid($oid);

Обратная совместимость и обновление

Начиная с версии 3.0 значения по умолчанию обновлены под актуальный API ЕСИА (Методические рекомендации): эндпоинты aas/oauth2/v2/ac и aas/oauth2/v3/te, portalUrl использует https://. Актуальный aas/oauth2/v3/te требует параметр clientCertificateHash. Если вы полагались на прежние значения по умолчанию (aas/oauth2/ac, aas/oauth2/te, http://), задайте их явно через Config.

Обновляетесь с 2.x? См. подробное руководство по обновлению 2.4.2 → 3.0.0.

Тестирование

Основное покрытие в библиотеке — офлайн, без обращения к ЕСИА (HTTP-моки, JWT-фикстуры, локальный ГОСТ round-trip и end-to-end против локального мок-сервера ЕСИА). Живой тестовый стенд esia-portal1.test.gosuslugi.ru доступен только из РФ и требует зарегистрированной ИС и ГОСТ-сертификата.

Быстрый запуск:

composer install
vendor/bin/codecept build   # генерирует акторов для наборов unit и e2e
vendor/bin/codecept run     # все наборы
vendor/bin/codecept run e2e # только end-to-end против мок-сервера

Подробнее — как устроено офлайн-тестирование, как запускать ГОСТ-тесты и на что библиотека настроена для реального стенда ЕСИА по умолчанию — в руководстве по тестированию (docs/testing.md).

Лицензия

Распространяется под лицензией MIT.