Как реализовать аутентификацию в API внешнего сервиса с использованием подписи сертификата в 1С?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При взаимодействии с внешними системами через API, особенно в сфере государственных или финансовых сервисов, часто возникает необходимость использовать механизмы строгой аутентификации. Одним из таких механизмов является аутентификация на основе проверки цифровой подписи, сформированной при помощи сертификатов открытых ключей — для этого подойдёт модуль подписания документов УКЭП через API. Рассмотрим подробнее, как реализовать такую аутентификацию в 1С на примере Личного кабинета ПРСД, где подпись передается через HTTP-заголовок X-FRSD-Authentication.

Задача состоит в том, чтобы программно из 1С сформировать HTTP-запрос к API, подписать его URL с помощью закрытого ключа, закодировать полученную подпись в Base64 и передать её в специальном HTTP-заголовке. Сервер, получив такой запрос, проверит подпись с использованием открытого ключа, зарегистрированного в его системе, и в случае успешной проверки предоставит доступ к запрашиваемым данным.

Первые шаги: попытка подключения и анализ ошибки HTTP 400 Bad Request

Изначально при работе с API и аутентификацией на основе подписей, особенно если вы сталкиваетесь с этим впервые, могут возникнуть сложности на этапе формирования запроса. Рассмотрим типовую начальную попытку, как показано в исходном сообщении, и проанализируем, почему она может быть неверной — для этого есть обработка для пошаговой отладки кода в 1С.


АдресСервера = "lk.frsd.ru/xapi/";
АдресРесурса = "/xapi/";

ДвоичныеДанныеСертификата = Сертификат.ДанныеСертификата.Получить;
ИмяФайлаДанныеСертификата = ПолучитьимяВременногоФайла("cer");
ДвоичныеДанныеСертификата.Записать(ИмяФайлаДанныеСертификата);

Заголовки = Новый Соответствие;
Заголовки.Вставить("POST " + АдресРесурса + " HTTP/1.1"); // Некорректное формирование заголовка
Заголовки.Вставить("Host", АдресСервера); // Неверное использование АдресСервера в Host
Заголовки.Вставить("X-FRSD-Authentication", ДвоичныеДанныеСертификата); // Основная ошибка

Соединение = Новый HTTPСоединение(АдресСервера,,,,,, Новый ЗащищенноеСоединениеOpenSSL, Ложь);
Запрос = Новый HTTPЗапрос(АдресРесурса, Заголовки);
Ответ = Соединение.ОтправитьДляОбработки(Запрос, ИмяВыходногоФайла);

В этом коде мы видим несколько критических ошибок, которые приведут к сбою:

  1. Некорректное формирование заголовка POST ... HTTP/1.1: Этот элемент является частью первой строки HTTP-запроса, а не отдельным заголовком. Объект HTTPЗапрос и HTTPСоединение в 1С автоматически формируют эту строку на основе переданных параметров. Вставлять ее вручную в коллекцию Заголовки не нужно.
  2. Неверное использование АдресСервера в заголовке Host: Заголовок Host должен содержать только доменное имя сервера (например, "lk.frsd.ru"), без пути к API. Сам объект HTTPСоединение ожидает базовый адрес сервера.
  3. Основная проблема: Передача двоичных данных сертификата в заголовок X-FRSD-Authentication: Согласно документации, в этот заголовок необходимо передавать подпись URL-запроса, причём в формате Base64. Прямая передача двоичных данных сертификата в HTTP-заголовок технически невозможна, так как заголовки должны содержать текстовые данные. Кроме того, нам нужна именно подпись, а не сам сертификат.

Эти ошибки, особенно последняя, приведут к получению от сервера ответа "Bad Request - Invalid Header HTTP Error 400. The request has an invalid header name.". Сервер не сможет разобрать некорректно сформированный заголовок или двоичные данные, переданные там, где ожидается текстовая строка.

Правильное формирование цифровой подписи URL-запроса

Итак, мы выяснили, что в заголовок X-FRSD-Authentication должна быть передана цифровая подпись URL-запроса, закодированная в Base64. Разберем по шагам, как это сделать:

1. Принцип цифровой подписи для аутентификации

Цифровая подпись работает по принципу асимметричной криптографии (алгоритмы ГОСТ или RSA). Мы используем пару ключей: закрытый (секретный) и открытый (публичный). Процесс выглядит так:

  1. Вы регистрируете свой открытый ключ в Личном кабинете ПРСД.
  2. Когда нужно отправить запрос, вы берете URL этого запроса (полностью, со всеми параметрами) и подписываете его своим закрытым ключом.
  3. Полученная подпись кодируется в формат Base64 и передается в HTTP-заголовке.
  4. Сервер получает запрос, извлекает URL и подпись. Используя ваш заранее зарегистрированный открытый ключ, он проверяет, что подпись была создана именно вашим закрытым ключом и что URL не был изменён в процессе передачи.

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

2. Формирование подписи (или подписание файла) в 1С с помощью МенеджерКриптографии

Для создания цифровой подписи в 1С мы будем использовать объект МенеджерКриптографии.

  1. Выбор сертификата:

    Нам потребуется объект типа СертификатМенеджерКриптографии, соответствующий вашему закрытому ключу, который зарегистрирован в системе ПРСД. Вы можете получить его, например, выбрав из хранилища текущего пользователя:

    
    МенеджерКриптографии = Новый МенеджерКриптографии;
    Сертификат = МенеджерКриптографии.ВыбратьСертификат(); // Пользователю будет предложен диалог выбора сертификата
    // Или получить по отпечатку, если сертификат известен:
    // Сертификат = МенеджерКриптографии.ПолучитьСертификат(ТипСертификата.Пользователя, ОтпечатокСертификата);
    Если Сертификат = Неопределено Тогда
        Сообщить("Выбор сертификата отменен или сертификат не найден.");
        Возврат;
    КонецЕсли;
    

    Убедитесь, что выбранный сертификат содержит закрытый ключ (свойство Сертификат.ЗакрытыйКлючУстановлен должно быть Истина).

  2. Подготовка подписываемой строки URL:

    Определяем полную строку URL, которую собираемся отправить в запросе. Это должна быть строка, начинающаяся с пути к ресурсу и включающая все GET-параметры.

    СтрокаURLДляЗапроса = "/xapi/doc-list.xml?od>=2024-05-23"; // Пример URL

    Очень важно: Строка для подписи должна быть идентична той, что будет использоваться в HTTPЗапрос. Обратите внимание на расширение .xml, если оно требуется API (это была одна из причин ошибки в исходной теме).

  3. Подписание данных:

    Используем метод Подписать() объекта МенеджерКриптографии (аналогично тому, как выполняется создание подписи ID-картой). Этот метод принимает строку или двоичные данные и объект сертификата, а возвращает двоичные данные подписи.

    
    ДвоичныеДанныеПодписи = МенеджерКриптографии.Подписать(СтрокаURLДляЗапроса, Сертификат);
    

    Объект МенеджерКриптографии автоматически выбирает подходящий алгоритм хеширования и подписи (например, как при подписи RSA с хешированием SHA512) на основе свойств сертификата и настроек криптопровайдера.

  4. Кодирование подписи в Base64 и очистка:

    Полученные двоичные данные подписи необходимо закодировать в текстовый формат Base64. Затем критически важно удалить из полученной Base64-строки все символы переноса строки и возврата каретки, так как они недопустимы в HTTP-заголовках и могут привести к ошибке "Bad Request".

    
    ЗакодированнаяПодписьBase64 = Base64Строка(ДвоичныеДанныеПодписи);
    // Удаляем символы переноса строки, которые могут быть добавлены при Base64-кодировании для больших данных
    ЗакодированнаяПодписьBase64 = СтрЗаменить(ЗакодированнаяПодписьBase64, Символы.ВК, "");
    ЗакодированнаяПодписьBase64 = СтрЗаменить(ЗакодированнаяПодписьBase64, Символы.ПС, "");
    

    Теперь у нас есть строка ЗакодированнаяПодписьBase64, готовая для передачи в заголовке.

Настройка HTTP-запроса в 1С с подписью

После того как мы получили корректную Base64-строку подписи, мы можем приступить к формированию и отправке HTTP-запроса.

  1. Инициализация HTTP-соединения:

    Создаем объект HTTPСоединение. Важно указать базовый адрес сервера (только домен, без пути к API) и использовать Новый ЗащищенноеСоединениеOpenSSL для работы по протоколу HTTPS — процесс ускоряет инструмент для технической организации HTTP-обмена.

    
    АдресСервераБезПути = "lk.frsd.ru";
    СоединениеHTTP = Новый HTTPСоединение(АдресСервераБезПути,,,,,, Новый ЗащищенноеСоединениеOpenSSL);
    
  2. Формирование заголовков:

    Создаем объект Соответствие для заголовков и вставляем туда нашу Base64-кодированную подпись под ключом "X-FRSD-Authentication".

    
    Заголовки = Новый Соответствие;
    Заголовки.Вставить("X-FRSD-Authentication", ЗакодированнаяПодписьBase64);
    
  3. Создание и отправка HTTP-запроса:

    Создаем объект HTTPЗапрос, передавая ему относительный путь к ресурсу (тот самый, который мы подписывали) и коллекцию заголовков. Затем отправляем запрос, используя метод ВызватьHTTPМетод() для GET-запроса.

    
    // СтрокаURLДляЗапроса должна быть идентична той, что использовалась для подписи
    СтрокаURLДляЗапроса = "/xapi/doc-list.xml?od>=2024-05-23"; 
    
    HTTPЗапрос = Новый HTTPЗапрос(СтрокаURLДляЗапроса, Заголовки);
    HTTPОтвет = СоединениеHTTP.ВызватьHTTPМетод("GET", HTTPЗапрос);
    
    КодОтвета = HTTPОтвет.КодСостояния;
    ОтветСервера = HTTPОтвет.ПолучитьТелоКакСтроку();
    
    Сообщить("Код ответа: " + КодОтвета);
    Сообщить("Тело ответа: " + ОтветСервера);
    

    Если запрос выполнен успешно, КодОтвета будет 200, и ОтветСервера будет содержать ожидаемые данные (например, XML со списком документов).

Диагностика и устранение ошибки HTTP 401 Unauthorized - Подпись запроса некорректна

Даже если мы правильно сформировали подпись, закодировали ее в Base64 и передали в заголовке, вы все равно можете столкнуться с ошибкой "HTTP Error 401 Unauthorized - Подпись запроса некорректна". Это означает, что сервер получил подпись, но не смог ее проверить. Вероятные причины:

  1. Несоответствие подписанного URL и фактически отправленного URL:

    Это самая частая и коварная причина ошибки 401. Сервер сверяет полученный URL с URL, который был использован для создания подписи. Если есть хоть малейшее расхождение, проверка не пройдет. Расхождения могут быть вызваны:

    • Пробелами или лишними символами: Случайно добавленные пробелы, символы табуляции или переноса строки в URL.
    • Регистром символов: Некоторые API чувствительны к регистру в URL или параметрах.
    • Порядком параметров: Если параметры в URL меняют свой порядок, подписанная строка изменится.
    • Отсутствием или наличием расширения файла: Как было обнаружено в исходной теме, отсутствие расширения .xml в запросе (например, /xapi/doc-list?od>=2024-05-23 вместо /xapi/doc-list.xml?od>=2024-05-23) приводило к ошибке. Если API ожидает конкретное расширение, оно должно быть частью подписанного URL и фактически отправленного URL.

    Для диагностики этой проблемы мы рекомендуем абсолютно точно убедиться, что строка, переданная в метод МенеджерКриптографии.Подписать(), и строка, переданная в конструктор HTTPЗапрос(), идентичны до последнего символа.

  2. Использование неверного сертификата:

    Убедитесь, что вы подписываете запрос закрытым ключом того сертификата, открытый ключ которого был зарегистрирован в Личном кабинете ПРСД.

  3. Проблемы с кодировкой строки:

    Хотя 1С обычно корректно работает со строками UTF-8, убедитесь, что при передаче строки URL для подписи и в самом запросе не происходит неявного изменения кодировки, особенно если URL содержит не-ASCII символы.

Финальный код для формирования подписи и отправки запроса в 1С

Объединим все шаги в единый пример кода, который формирует подпись URL-запроса средствами 1С и отправляет его во внешний сервис:


// 1. Получение сертификата (с установленным закрытым ключом)
МенеджерКриптографии = Новый МенеджерКриптографии;
Сертификат = МенеджерКриптографии.ВыбратьСертификат(); // Или получить по отпечатку

Если Сертификат = Неопределено Тогда
    Сообщить("Выбор сертификата отменен или сертификат не найден. Проверьте наличие сертификата.");
    Возврат;
КонецЕсли;

Если Не Сертификат.ЗакрытыйКлючУстановлен Тогда
    Сообщить("У выбранного сертификата отсутствует закрытый ключ. Невозможно сформировать подпись.");
    Возврат;
КонецЕсли;

// 2. Определение строки URL для запроса (критически важно, чтобы она точно совпадала!)
// В данном примере: GET /xapi/doc-list.xml?od>=2024-05-23
// Обратите внимание на расширение .xml, если оно требуется API
СтрокаURLДляЗапроса = "/xapi/doc-list.xml?od>=2024-05-23";

// 3. Формирование цифровой подписи
Попытка
    ДвоичныеДанныеПодписи = МенеджерКриптографии.Подписать(СтрокаURLДляЗапроса, Сертификат);
Исключение
    Сообщить("Ошибка при формировании подписи: " + ОписаниеОшибки());
    Возврат;
КонецПопытки;

// 4. Кодирование подписи в Base64 и удаление символов переноса строки
ЗакодированнаяПодписьBase64 = Base64Строка(ДвоичныеДанныеПодписи);
ЗакодированнаяПодписьBase64 = СтрЗаменить(ЗакодированнаяПодписьBase64, Символы.ВК, "");
ЗакодированнаяПодписьBase64 = СтрЗаменить(ЗакодированнаяПодписьBase64, Символы.ПС, "");

// 5. Настройка HTTP-соединения
АдресСервераБезПути = "lk.frsd.ru";
СоединениеHTTP = Новый HTTPСоединение(АдресСервераБезПути,,,,,, Новый ЗащищенноеСоединениеOpenSSL);

// 6. Формирование заголовков HTTP-запроса
Заголовки = Новый Соответствие;
Заголовки.Вставить("X-FRSD-Authentication", ЗакодированнаяПодписьBase64);

// 7. Создание и отправка HTTP-запроса
// Строка URL здесь должна быть ИДЕНТИЧНА СтрокаURLДляЗапроса, использованной для подписи
HTTPЗапрос = Новый HTTPЗапрос(СтрокаURLДляЗапроса, Заголовки);
Попытка
    HTTPОтвет = СоединениеHTTP.ВызватьHTTPМетод("GET", HTTPЗапрос);

    КодОтвета = HTTPОтвет.КодСостояния;
    ОтветСервера = HTTPОтвет.ПолучитьТелоКакСтроку();

    Сообщить("Код ответа: " + КодОтвета);
    Сообщить("Тело ответа: " + ОтветСервера);

    Если КодОтвета = 200 Тогда
        Сообщить("Запрос успешно выполнен.");
        // Если ответ в XML, можно его распарсить
        Попытка
            ЧтениеXML = Новый ЧтениеXML;
            ЧтениеXML.УстановитьСтроку(ОтветСервера);
            РезультатXML = ПрочитатьXML(ЧтениеXML);
            ЧтениеXML.Закрыть();
            Сообщить("Данные XML получены и обработаны.");
            // Дальнейшая работа с РезультатXML
        Исключение
            Сообщить("Ошибка при чтении XML ответа: " + ОписаниеОшибки());
        КонецПопытки;
    Иначе
        Сообщить("Ошибка при выполнении запроса. Код: " + КодОтвета + ", Ответ: " + ОтветСервера);
    КонецЕсли;

Исключение
    Сообщить("Произошла ошибка при отправке HTTP-запроса: " + ОписаниеОшибки());
КонецПопытки;

Этот комплексный подход позволяет успешно реализовать аутентификацию по цифровой подписи в 1С. Ключевыми моментами являются точное соблюдение формата подписываемых данных (URL запроса), правильное использование МенеджерКриптографии для создания подписи, корректное Base64-кодирование с очисткой от лишних символов и внимательное формирование HTTP-запроса, чтобы подписанный URL и фактически отправленный URL были идентичны.

Если вы сталкиваетесь с проблемами, рекомендуем внимательно проверять:

  1. Логи сервера (если есть доступ) на предмет того, какой URL и подпись были получены.
  2. Что строка, которую вы передаете в МенеджерКриптографии.Подписать(), абсолютно идентична строке, которую вы передаете в конструктор HTTPЗапрос().
  3. Что ваш сертификат действителен, содержит закрытый ключ и зарегистрирован в Личном кабинете ПРСД.
← На главную