При взаимодействии с внешними системами через API, особенно в сфере государственных или финансовых сервисов, часто возникает необходимость использовать механизмы строгой аутентификации. Одним из таких механизмов является аутентификация на основе проверки цифровой подписи, сформированной при помощи сертификатов открытых ключей — для этого подойдёт модуль подписания документов УКЭП через API. Рассмотрим подробнее, как реализовать такую аутентификацию в 1С на примере Личного кабинета ПРСД, где подпись передается через HTTP-заголовок X-FRSD-Authentication.
Задача состоит в том, чтобы программно из 1С сформировать HTTP-запрос к API, подписать его URL с помощью закрытого ключа, закодировать полученную подпись в Base64 и передать её в специальном HTTP-заголовке. Сервер, получив такой запрос, проверит подпись с использованием открытого ключа, зарегистрированного в его системе, и в случае успешной проверки предоставит доступ к запрашиваемым данным.
Изначально при работе с API и аутентификацией на основе подписей, особенно если вы сталкиваетесь с этим впервые, могут возникнуть сложности на этапе формирования запроса. Рассмотрим типовую начальную попытку, как показано в исходном сообщении, и проанализируем, почему она может быть неверной — для этого есть обработка для пошаговой отладки кода в 1С.
АдресСервера = "lk.frsd.ru/xapi/";
АдресРесурса = "/xapi/";
ДвоичныеДанныеСертификата = Сертификат.ДанныеСертификата.Получить;
ИмяФайлаДанныеСертификата = ПолучитьимяВременногоФайла("cer");
ДвоичныеДанныеСертификата.Записать(ИмяФайлаДанныеСертификата);
Заголовки = Новый Соответствие;
Заголовки.Вставить("POST " + АдресРесурса + " HTTP/1.1"); // Некорректное формирование заголовка
Заголовки.Вставить("Host", АдресСервера); // Неверное использование АдресСервера в Host
Заголовки.Вставить("X-FRSD-Authentication", ДвоичныеДанныеСертификата); // Основная ошибка
Соединение = Новый HTTPСоединение(АдресСервера,,,,,, Новый ЗащищенноеСоединениеOpenSSL, Ложь);
Запрос = Новый HTTPЗапрос(АдресРесурса, Заголовки);
Ответ = Соединение.ОтправитьДляОбработки(Запрос, ИмяВыходногоФайла);
В этом коде мы видим несколько критических ошибок, которые приведут к сбою:
POST ... HTTP/1.1: Этот элемент является частью первой строки HTTP-запроса, а не отдельным заголовком. Объект HTTPЗапрос и HTTPСоединение в 1С автоматически формируют эту строку на основе переданных параметров. Вставлять ее вручную в коллекцию Заголовки не нужно.АдресСервера в заголовке Host: Заголовок Host должен содержать только доменное имя сервера (например, "lk.frsd.ru"), без пути к API. Сам объект HTTPСоединение ожидает базовый адрес сервера.X-FRSD-Authentication: Согласно документации, в этот заголовок необходимо передавать подпись URL-запроса, причём в формате Base64. Прямая передача двоичных данных сертификата в HTTP-заголовок технически невозможна, так как заголовки должны содержать текстовые данные. Кроме того, нам нужна именно подпись, а не сам сертификат.Эти ошибки, особенно последняя, приведут к получению от сервера ответа "Bad Request - Invalid Header HTTP Error 400. The request has an invalid header name.". Сервер не сможет разобрать некорректно сформированный заголовок или двоичные данные, переданные там, где ожидается текстовая строка.
Итак, мы выяснили, что в заголовок X-FRSD-Authentication должна быть передана цифровая подпись URL-запроса, закодированная в Base64. Разберем по шагам, как это сделать:
Цифровая подпись работает по принципу асимметричной криптографии (алгоритмы ГОСТ или RSA). Мы используем пару ключей: закрытый (секретный) и открытый (публичный). Процесс выглядит так:
Важно: Подписывается именно строка URL запроса, а не содержимое сертификата или какой-либо другой файл. Подписываемая строка URL должна в точности совпадать с той строкой URL, которая будет отправлена в HTTP-запросе. Любое расхождение приведет к ошибке проверки подписи.
МенеджерКриптографииДля создания цифровой подписи в 1С мы будем использовать объект МенеджерКриптографии.
Нам потребуется объект типа СертификатМенеджерКриптографии, соответствующий вашему закрытому ключу, который зарегистрирован в системе ПРСД. Вы можете получить его, например, выбрав из хранилища текущего пользователя:
МенеджерКриптографии = Новый МенеджерКриптографии;
Сертификат = МенеджерКриптографии.ВыбратьСертификат(); // Пользователю будет предложен диалог выбора сертификата
// Или получить по отпечатку, если сертификат известен:
// Сертификат = МенеджерКриптографии.ПолучитьСертификат(ТипСертификата.Пользователя, ОтпечатокСертификата);
Если Сертификат = Неопределено Тогда
Сообщить("Выбор сертификата отменен или сертификат не найден.");
Возврат;
КонецЕсли;
Убедитесь, что выбранный сертификат содержит закрытый ключ (свойство Сертификат.ЗакрытыйКлючУстановлен должно быть Истина).
Определяем полную строку URL, которую собираемся отправить в запросе. Это должна быть строка, начинающаяся с пути к ресурсу и включающая все GET-параметры.
СтрокаURLДляЗапроса = "/xapi/doc-list.xml?od>=2024-05-23"; // Пример URL
Очень важно: Строка для подписи должна быть идентична той, что будет использоваться в HTTPЗапрос. Обратите внимание на расширение .xml, если оно требуется API (это была одна из причин ошибки в исходной теме).
Используем метод Подписать() объекта МенеджерКриптографии (аналогично тому, как выполняется создание подписи ID-картой). Этот метод принимает строку или двоичные данные и объект сертификата, а возвращает двоичные данные подписи.
ДвоичныеДанныеПодписи = МенеджерКриптографии.Подписать(СтрокаURLДляЗапроса, Сертификат);
Объект МенеджерКриптографии автоматически выбирает подходящий алгоритм хеширования и подписи (например, как при подписи RSA с хешированием SHA512) на основе свойств сертификата и настроек криптопровайдера.
Полученные двоичные данные подписи необходимо закодировать в текстовый формат Base64. Затем критически важно удалить из полученной Base64-строки все символы переноса строки и возврата каретки, так как они недопустимы в HTTP-заголовках и могут привести к ошибке "Bad Request".
ЗакодированнаяПодписьBase64 = Base64Строка(ДвоичныеДанныеПодписи);
// Удаляем символы переноса строки, которые могут быть добавлены при Base64-кодировании для больших данных
ЗакодированнаяПодписьBase64 = СтрЗаменить(ЗакодированнаяПодписьBase64, Символы.ВК, "");
ЗакодированнаяПодписьBase64 = СтрЗаменить(ЗакодированнаяПодписьBase64, Символы.ПС, "");
Теперь у нас есть строка ЗакодированнаяПодписьBase64, готовая для передачи в заголовке.
После того как мы получили корректную Base64-строку подписи, мы можем приступить к формированию и отправке HTTP-запроса.
Создаем объект HTTPСоединение. Важно указать базовый адрес сервера (только домен, без пути к API) и использовать Новый ЗащищенноеСоединениеOpenSSL для работы по протоколу HTTPS — процесс ускоряет инструмент для технической организации HTTP-обмена.
АдресСервераБезПути = "lk.frsd.ru";
СоединениеHTTP = Новый HTTPСоединение(АдресСервераБезПути,,,,,, Новый ЗащищенноеСоединениеOpenSSL);
Создаем объект Соответствие для заголовков и вставляем туда нашу Base64-кодированную подпись под ключом "X-FRSD-Authentication".
Заголовки = Новый Соответствие;
Заголовки.Вставить("X-FRSD-Authentication", ЗакодированнаяПодписьBase64);
Создаем объект 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 со списком документов).
Даже если мы правильно сформировали подпись, закодировали ее в Base64 и передали в заголовке, вы все равно можете столкнуться с ошибкой "HTTP Error 401 Unauthorized - Подпись запроса некорректна". Это означает, что сервер получил подпись, но не смог ее проверить. Вероятные причины:
Это самая частая и коварная причина ошибки 401. Сервер сверяет полученный URL с URL, который был использован для создания подписи. Если есть хоть малейшее расхождение, проверка не пройдет. Расхождения могут быть вызваны:
.xml в запросе (например, /xapi/doc-list?od>=2024-05-23 вместо /xapi/doc-list.xml?od>=2024-05-23) приводило к ошибке. Если API ожидает конкретное расширение, оно должно быть частью подписанного URL и фактически отправленного URL.Для диагностики этой проблемы мы рекомендуем абсолютно точно убедиться, что строка, переданная в метод МенеджерКриптографии.Подписать(), и строка, переданная в конструктор HTTPЗапрос(), идентичны до последнего символа.
Убедитесь, что вы подписываете запрос закрытым ключом того сертификата, открытый ключ которого был зарегистрирован в Личном кабинете ПРСД.
Хотя 1С обычно корректно работает со строками UTF-8, убедитесь, что при передаче строки URL для подписи и в самом запросе не происходит неявного изменения кодировки, особенно если URL содержит не-ASCII символы.
Объединим все шаги в единый пример кода, который формирует подпись 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 были идентичны.
Если вы сталкиваетесь с проблемами, рекомендуем внимательно проверять:
МенеджерКриптографии.Подписать(), абсолютно идентична строке, которую вы передаете в конструктор HTTPЗапрос().