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


    Представляем yet another PHP-пакет интеграции с ЕСИА — ekapusta/oauth2-esia. Реализован как адаптер к популярному league/oauth2-client.


    Оргмоменты


    Интеграция с ЕСИА затрагивает госорганы, финансовые и страховые компании, кредитные организации (банки, микрофинансы), организации с публичным wi-fi'ем и прочих, кому в будущем правительство даст добро. Основные оргмоменты описаны на Хабре по запросу "ЕСИА", актуальные версии документов доступны на оф. сайте, а поддержку можно получить в относительно разумные сроки через esia@minsvyaz.ru.


    Почему этот пакет?


    Build Status Coverage Status Software License


    • Получение основных и вложенных данных гражданина делается в один запрос, используя доступные в ЕСИА embeds. Т.е. для получения контактов/адресов не надо будет делать ещё N отдельных запросов.
    • Два варианта подписи запроса: через CLI и через встроенные PHP-функции. Что позволяет работать по RSA и по GOST'у. CLI подписывателю не нужна временная директория.
    • Подпись токена доступа проверяется публичными ключами ЕСИА.
    • Основан на покрытом тестами league/oauth2-client, к которому является адаптером, не изобретая велосипеды и сам покрыт тестами на 100%.
    • В тестах есть элементы интеграции — аутентификационный бот, который ходит в реальный тестовый стенд. Спасибо headless-chrome'у.
    • Генерация стейта и разбор JWT токена использует пакеты, специально для этого написанные: ramsey/uuid и lcobucci/jwt.
    • Совместим с PHP ^ 5.6 || ^ 7.0.

    Покажите код!


    Конфигурируем


    use Ekapusta\OAuth2Esia\Provider\EsiaProvider;
    use Ekapusta\OAuth2Esia\Security\Signer\OpensslPkcs7;
    
    $provider = new EsiaProvider([
        'clientId'      => 'XXXXXX', // "Мненомика" в терминах ЕСИА
        'redirectUri'   => 'https://your-system.domain/auth/finish/',
        'defaultScopes' => ['openid', 'fullname', '...'], // Скоупы описаны в методичке
    // Для работы с тестовой версией портала
    //  'remoteUrl' => 'https://esia-portal1.test.gosuslugi.ru',
    //  'remoteCertificatePath' => EsiaProvider::RESOURCES.'esia.test.cer',
    ], [
        'signer' => new OpensslPkcs7('/path/to/public/certificate.cer', '/path/to/private.key')
    ]);

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


    • Если вы используете ключи RSA, достаточно OpensslPkcs7.
    • Если вы используете ключи GOST и скомпилировали PHP с шифрами ГОСТ, то достаточно OpensslPkcs7.
    • Если вы используете ключи GOST и имеете инструмент, совместимый с openssl, используйте OpensslCli. Он имеет параметр «toolpath».
    • Если вы используете ключи GOST и фанат докера, вы можете использовать OpensslCli с параметром 'toolpath' => 'docker run --rm -i -v $(pwd):$(pwd) -w $(pwd) rnix/openssl-gost openssl'.

    Редиректим посетителя на ЕСИА


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


    // Где-то страничке https://your-system.domain/auth/start/
    $authUrl = $provider->getAuthorizationUrl();
    $_SESSION['oauth2.esia.state'] = $provider->getState();
    header('Location: '.$authUrl);
    exit;

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


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


    // Где-то страничке https://your-system.domain/auth/finish/?state=...&code=...
    if ($_SESSION['oauth2.esia.state'] !== $_GET['state']) {
        exit('The guard unravels the crossword.');
    }
    
    $token = $provider->getAccessToken('authorization_code', ['code' => $_GET['code']]);
    $esiaPersonData = $provider->getResourceOwner($accessToken);
    var_export($esiaPersonData->toArray());

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


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


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


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


    UPD1


    Бонусом идёт symfony-бандл ekapusta/oauth2-esia-bundle:


    • php: ^5.6 || ^7.0
    • symfony: ^2.8 || ^3 || ^4.
    Поделиться публикацией

    Похожие публикации

    Комментарии 13
      +1

      При использовании криптографических алгоритмов GOST первоначальный редирект на ЕСИА получается длиной примерно 5000+ символов. Вы не пытались как-то уменьшить это значение? Некоторым браузерам не нравятся такие длинные ссылки.

        +1

        Там не пять тыщ, а где-то две. Можно добавить параметр типа "-noattr", сколько-то сэкономит. Пробовал "-nocert", но удалённая система хочет сертификаты в подписи. Поэтому только noattr.

          +1

          -noattr я использую — без него 5600+ символов получается. Как я понимаю OpenSSL у вас вызывается с вот этими аргументами?
          Две тысячи символов у меня только с использованием RSA.


          Добавление -nocert уменьшает редирект на 3461 символ, но авторизация тогда не проходит.

            0

            Можно попробовать поиграться с самим сертификатом, попытавшись сделать его обрезанную версию. Напр. вот такой тулзой: https://github.com/pornin/DDer


            Однако спецификация сертификатов нам говорит, что практически всё в нём required.


            Гигантский размер скорее всего связан с utf в subject'е, который никак не поменяешь т.к. отпечаток сертификата/ключа на него завязан.

              0

              Добавил подписывателям -noattr в релизе 1.1.2.

            +1

            Ах да! Никто не требует, чтобы редирект был именно GET'овым.
            POST'ом тоже всё прекрасно работает.

          +1
          Расскажите пожалуйста подробнее о параметрах remoteCertificatePath и remoteUrl. remoteUrl по умолчанию какой?
            +1

            URL — это адрес удалённой системы. По-умолчанию боевой. Чтобы использовать тестовый, подставляете 'https://esia-portal1.test.gosuslugi.ru'.


            Сертификат — это публичный сертификат с ключом для проверки подписи токена. По-умолчанию боевой (идёт в комплекте). Я не понимаю, почему минсвязь его в PEM-виде до сих не добавила в методичку. Надеюсь осознают и добавят.


            На данный момент по факту только RSA в подписи токена используется, хотя в методичке пишут, что и GOST возможен.


            https://github.com/ekapusta/oauth2-esia/blob/5d141d943ac7fef8aac71ca432884ac9fa013768/src/Provider/EsiaProvider.php#L28:L30


            protected $remoteUrl = 'https://esia.gosuslugi.ru';
            protected $remoteCertificatePath = self::RESOURCES.'esia.prod.cer';
              +1
              А есть ли разница в сертификате/ключе, сгенеренным для SAML и для OAuth? Через SAML(simplesamlphp) все отлично работает, но закрытый ключ почему-то не подходит для oauth2-esia с руганью openssl_pkey_get_private. Или это openssl ерундит?
              За ключ в комплекте отдельное спасибо, не нашел упоминаний в Методических рекомендациях, хотя сама документация написана хорошо.

        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

        Самое читаемое