Как настроить авторизацию в API Честного Знака из 1С и победить ошибку 307 Temporary Redirect

Программист 1С v8.3 (Обычные формы) Торговля и дистрибуция
← На главную

Интеграция с государственной системой маркировки «Честный Знак» (ГИС МТ) — одна из наиболее частых задач для разработчиков 1С в последнее время. Процесс авторизации в True API «Честного Знака» построен на механизме двухфакторного подтверждения с использованием электронной цифровой подписи (ЭЦП). На практике этот процесс часто вызывает трудности: от непонятных редиректов веб-сервера до ошибок проверки криптографической подписи. В этой статье мы подробно разберем, как правильно реализовать весь цикл авторизации, исправить популярные ошибки и предоставим готовые примеры кода для различных методов реализации.

Общая схема авторизации в True API Честного Знака

Прежде чем переходить к коду, давайте разберем логику работы API. Авторизация происходит в три этапа:

  1. Получение данных для подписания (GET-запрос): Наша система отправляет GET-запрос к API и получает в ответ уникальный идентификатор сессии (uuid) и случайную строку данных (data).
  2. Подписание полученных данных: Мы подписываем строку data на нашей стороне с помощью сертификата ЭЦП организации. Подпись должна быть присоединенной (в формате PKCS#7 / CAdES-BES) и закодированной в Base64.
  3. Получение токена (POST-запрос): Мы отправляем POST-запрос на метод simpleSignIn, передавая в теле JSON-объект с исходным uuid и нашей полученной подписью. В ответ система присылает JWT-токен, который мы будем использовать для всех последующих запросов.

Выясняем причины ошибки 307 Temporary Redirect

В самом начале работы многие разработчики сталкиваются с тем, что вместо JSON-ответа от сервера приходит HTML-страница с ошибкой 307 Temporary Redirect. Проанализируем ситуацию. Веб-сервер Nginx, установленный на стороне «Честного Знака», выполняет перенаправление по следующим причинам:

Почему подписанная строка data весит около 5000 символов?

У разработчиков часто возникает вопрос: почему исходная строка data была короткой, а после подписания она превратилась в огромный текст длиной в 5000–8000 символов? Пугаться этого не нужно — это абсолютно правильное поведение.

Дело в том, что «Честный Знак» требует именно присоединенную подпись. Такая подпись содержит не только криптографический хэш исходных данных, но и открытый ключ самого сертификата, сведения о владельце, цепочку доверия и прочую метаинформацию. Все это упаковывается в бинарный контейнер PKCS#7 и кодируется в Base64, что и увеличивает размер строки в разы. В примерах официальной документации ЧЗ часто приводят короткие фиктивные строки просто для наглядности структуры JSON.

Очистка подписи от символов переноса строк (Критически важно!)

Любые криптографические инструменты (будь то утилиты КриптоПро или COM-объекты) при экспорте результата в формат Base64 автоматически разбивают длинную строку на строки по 64 или 76 символов, разделяя их невидимыми символами перевода строки (\r\n). Если отправить данные в таком виде в JSON-запросе, сервер вернет ошибку разбора структуры пакета.

Перед отправкой подписи обязательно очищайте ее от всех служебных символов переноса строки и возврата каретки:


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

---

Способ 1. Подписание данных встроенным Менеджером Криптографии 1С (Рекомендуемый)

Если вы работаете на современной платформе 1С:Предприятие 8.3, самым надежным и переносимым решением будет использование встроенного объекта МенеджерКриптографии. Этот способ работает без использования внешних библиотек и одинаково хорошо применим как на клиенте, так и на сервере (если КриптоПро установлен на сервере 1С).

Рассмотрим по шагам программный код для подписания данных:


&НаСервере
Функция ПодписатьСтрокуВстроеннымиСредствами(СлучайнаяСтрока, ОтпечатокСертификата) Экспорт
    
    // Инициализируем менеджер криптографии для ГОСТ Р 34.10-2012 (тип провайдера 75 или 80)
    Менеджер = Новый МенеджерКриптографии("", "", 75);
    
    // Ищем нужный сертификат в хранилище операционной системы по отпечатку
    СертификатЭЦП = Неопределено;
    СписокСертификатов = Менеджер.ПолучитьСписокСертификатов();
    
    // Очищаем отпечаток от пробелов для точного сравнения
    ПоискОтпечатка = ВРег(СтрЗаменить(ОтпечатокСертификата, " ", ""));
    
    Для Каждого ТекСертификат Из СписокСертификатов Цикл
        Если ВРег(ТекСертификат.Отпечаток) = ПоискОтпечатка Тогда
            СертификатЭЦП = ТекСертификат;
            Прервать;
        КонецЕсли;
    КонецЦикла;
    
    Если СертификатЭЦП = Неопределено Тогда
        ВызватьИсключение "Указанный сертификат ЭЦП не найден в личном хранилище системы!";
    КонецЕсли;
    
    // Преобразуем полученную из ЧЗ строку в двоичные данные в кодировке UTF-8
    ДанныеДляПодписи = ПолучитьДвоичныеДанныеИзСтроки(СлучайнаяСтрока, КодировкаТекста.UTF8);
    
    // Метод Подписать() платформы 1С по умолчанию создает присоединенную подпись PKCS#7
    ДвоичныеДанныеПодписи = Менеджер.Подписать(ДанныеДляПодписи, СертификатЭЦП);
    
    // Кодируем результат в формат Base64
    ПодписьBase64 = ДвоичныеДанныеBase64(ДвоичныеДанныеПодписи);
    
    // Очищаем полученную строку от переносов
    ПодписьBase64 = СтрЗаменить(ПодписьBase64, Символы.ПС, "");
    ПодписьBase64 = СтрЗаменить(ПодписьBase64, Символы.ВК, "");
    
    Возврат ПодписьBase64;
    
КонецФункции

---

Способ 2. Подписание данных через COM-объект CAdESCOM (Для обычных форм и старых конфигураций)

Если по каким-то причинам встроенный менеджер криптографии вам не подходит, либо вы ведете разработку на старых версиях платформы (включая 1С 7.7), отличной альтернативой станет использование COM-компоненты КриптоПро — CAdESCOM.

Разберем готовые функции для работы с объектами CAdESCOM.Store и CAdESCOM.CPSigner:


// Функция ищет сертификат в личном хранилище текущего пользователя по шестнадцатеричному отпечатку
Функция ПолучитьСертификатCAdESCOM(Отпечаток)
    
    CAPICOM_CURRENT_USER_STORE = 2; // Искать в ветке "Текущий пользователь"
    CAPICOM_MY_STORE           = "My"; // Искать в папке "Личное"
    CAPICOM_STORE_OPEN_READ_ONLY = 0; // Открывать только для чтения
    CAPICOM_CERTIFICATE_FIND_SHA1_HASH = 0; // Поиск по SHA1 хэшу (отпечатку)
    
    oStore = Новый COMОбъект("CAdESCOM.Store");
    oStore.Open(CAPICOM_CURRENT_USER_STORE, CAPICOM_MY_STORE, CAPICOM_STORE_OPEN_READ_ONLY);
    
    // Очищаем переданный отпечаток от возможных пробелов
    ЧистыйОтпечаток = СтрЗаменить(Отпечаток, " ", "");
    
    НайденныеСертификаты = oStore.Certificates.Find(CAPICOM_CERTIFICATE_FIND_SHA1_HASH, ЧистыйОтпечаток);
    
    Если НайденныеСертификаты.Count = 0 Тогда
        oStore.Close();
        ВызватьИсключение "Сертификат с указанным отпечатком не найден!";
    КонецЕсли;
    
    ВыбранныйСертификат = НайденныеСертификаты.Item(1);
    oStore.Close();
    
    Возврат ВыбранныйСертификат;
    
КонецФункции

// Функция формирует присоединенную подпись в Base64 для переданного текста
Функция ПодписатьТекстCAdESCOM(ТекстДляПодписи, Отпечаток) Экспорт
    
    CADESCOM_BASE64_TO_BINARY = 1; // Входные данные передаются в Base64 (или мы сконвертируем их предварительно)
    CADESCOM_CADES_TYPE = 1; // Тип усовершенствованной подписи (CAdES-BES)
    bDetached = Ложь; // Указываем Ложь, чтобы получить ПРИСОЕДИНЕННУЮ подпись
    
    // Создаем объект параметров подписания
    oSigner = Новый COMОбъект("CAdESCOM.CPSigner");
    oSigner.Certificate = ПолучитьСертификатCAdESCOM(Отпечаток);
    
    // Настраиваем объект данных
    oSignedData = Новый COMОбъект("CAdESCOM.CadesSignedData");
    oSignedData.ContentEncoding = CADESCOM_BASE64_TO_BINARY;
    
    // Кодируем исходный текст в Base64 перед передачей в CAdESCOM
    ВременныеДвоичныеДанные = ПолучитьДвоичныеДанныеИзСтроки(ТекстДляПодписи, КодировкаТекста.UTF8);
    ТекстBase64 = ДвоичныеДанныеBase64(ВременныеДвоичныеДанные);
    
    oSignedData.Content = ТекстBase64;
    
    // Выполняем подписание
    СтрокаПодписи = oSignedData.SignCades(oSigner, CADESCOM_CADES_TYPE, bDetached);
    
    // Очищаем подпись от нежелательных переносов строк
    СтрокаПодписи = СтрЗаменить(СтрокаПодписи, Символы.ПС, "");
    СтрокаПодписи = СтрЗаменить(СтрокаПодписи, Символы.ВК, "");
    
    Возврат СтрокаПодписи;
    
КонецФункции

---

Способ 3. Использование консольной утилиты csptest.exe (Для 1С 7.7 и файловых систем)

В старых конфигурациях 1С или при отсутствии возможности зарегистрировать COM-объекты, разработчики часто прибегают к вызову консольной утилиты, поставляемой вместе с КриптоПро CSP — csptest.exe.

При использовании этого метода крайне важно учитывать два нюанса:

Посмотрим на пример реализации вызова утилиты:


Функция ПодписатьЧерезCspTest(ИсходныеДанные, Отпечаток, КаталогВременныхФайлов)
    
    ИмяФайлаВход = КаталогВременныхФайлов + "data_in.tmp";
    ИмяФайлаВыход = КаталогВременныхФайлов + "data_out.tmp";
    
    // Записываем исходные данные во временный файл
    ТекстДок = Новый ТекстовыйДокумент;
    ТекстДок.УстановитьТекст(ИсходныеДанные);
    ТекстДок.Записать(ИмяФайлаВход, КодировкаТекста.UTF8);
    
    // Формируем командную строку. Путь к csptest.exe обязательно оборачиваем в кавычки
    ПутьККриптоПро = """C:\Program Files (x86)\Crypto Pro\CSP\csptest.exe""";
    ЧистыйОтпечаток = СтрЗаменить(Отпечаток, " ", "");
    
    ПараметрыЗапуска = " -sfsign -sign -in """ + ИмяФайлаВход + """ -out """ + ИмяФайлаВыход + """ -my " + ЧистыйОтпечаток + " -base64 -add";
    ПолнаяКоманда = ПутьККриптоПро + ПараметрыЗапуска;
    
    // Запускаем процесс синхронно, ожидая завершения (третий параметр равен Истина)
    ПодключитьВнешнююКомпоненту("WScript.Shell");
    Shell = Новый COMОбъект("WScript.Shell");
    КодВозврата = Shell.Run(ПолнаяКоманда, 0, Истина);
    
    Если КодВозврата <> 0 Тогда
        ВызватьИсключение "Ошибка выполнения команды csptest.exe! Код ошибки: " + Строка(КодВозврата);
    КонецЕсли;
    
    // Считываем результат подписания
    РезультатДок = Новый ТекстовыйДокумент;
    РезультатДок.Прочитать(ИмяФайлаВыход, КодировкаТекста.UTF8);
    СтрокаПодписи = РезультатДок.ПолучитьТекст();
    
    // Очищаем полученный Base64 от переводов строк
    СтрокаПодписи = СтрЗаменить(СтрокаПодписи, Символы.ПС, "");
    СтрокаПодписи = СтрЗаменить(СтрокаПодписи, Символы.ВК, "");
    
    // Удаляем временные файлы за собой
    Попытка
        УдалитьФайлы(ИмяФайлаВход);
        УдалитьФайлы(ИмяФайлаВыход);
    Исключение
    КонецПопытки;
    
    Возврат СтрокаПодписи;
    
КонецФункции

---

Отправка финального POST-запроса на получение токена

После того, как мы успешно получили строку подписи и очистили её от переносов, нам необходимо сформировать JSON-запрос и отправить его на сервер. Обратите внимание, что согласно актуальной спецификации True API «Честного Знака», отправлять данные нужно в виде одиночного JSON-объекта (квадратные скобки из старых версий документации использовать не нужно).

Пример формирования POST-запроса к API:


Функция ПолучитьТокенАвторизации(АдресСервера, UUIDСессии, ПодписьBase64)
    
    // Формируем JSON-тело запроса
    ЗаписьJSON = Новый ЗаписьJSON;
    ЗаписьJSON.УстановитьСтроку();
    
    ПараметрыЗапроса = Новый Структура;
    ПараметрыЗапроса.Вставить("uuid", UUIDСессии);
    ПараметрыЗапроса.Вставить("data", ПодписьBase64);
    
    ЗаписатьJSON(ЗаписьJSON, ПараметрыЗапроса);
    СтрокаЗапросаJSON = ЗаписьJSON.Закрыть();
    
    // Инициализируем HTTP-соединение (не используйте слэш в конце адреса!)
    // АдресСервера должен быть вида: "markirovka.crpt.ru"
    НТТРСоединение = Новый HTTPСоединение(АдресСервера, 443, , , , , Новый ЗащищенноеСоединениеOpenSSL());
    
    // Настраиваем POST-запрос
    HTTPЗапрос = Новый HTTPЗапрос("/api/v3/true-api/auth/simpleSignIn");
    HTTPЗапрос.Заголовки.Вставить("Content-Type", "application/json; charset=UTF-8");
    HTTPЗапрос.УстановитьТелоИзСтроки(СтрокаЗапросаJSON, КодировкаТекста.UTF8);
    
    Попытка
        HTTPОтвет = НТТРСоединение.ОтправитьДляОбработки(HTTPЗапрос);
        КодСостояния = HTTPОтвет.КодСостояния;
        ТелоОтвета = HTTPОтвет.ПолучитьТелоКакСтроку(КодировкаТекста.UTF8);
        
        Если КодСостояния = 200 Тогда
            // Извлекаем токен из JSON-ответа сервера
            ЧтениеJSON = Новый ЧтениеJSON;
            ЧтениеJSON.УстановитьСтроку(ТелоОтвета);
            ДанныеОтвета = ПрочитатьJSON(ЧтениеJSON);
            ЧтениеJSON.Закрыть();
            
            Возврат ДанныеОтвета.token; // Наш искомый JWT-токен
        Иначе
            ВызватьИсключение "Ошибка авторизации ЧЗ! Код: " + Строка(КодСостояния) + ", Ответ: " + ТелоОтвета;
        КонецЕсли;
    Исключение
        ВызватьИсключение "Не удалось соединиться с сервером Честного Знака: " + ОписаниеОшибки();
    КонецПопытки;
    
КонецФункции

Мы полностью разобрали процесс авторизации в True API «Честного Знака». Используя встроенные методы платформы 1С или внешние криптографические компоненты, вы сможете надежно реализовать процесс получения токена без вылетов программы и досадных системных ошибок.

← На главную