Почему при HTTP-запросе из 1С постоянно приходят разные Set-Cookie и не проходит авторизация

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

Разработка интеграций со сторонними веб-сервисами с использованием HTTP-запросов — одна из наиболее частых задач для программиста 1С. Однако при переносе запросов из специализированных утилит (таких как Postman, Insomnia или обычный веб-браузер) в код 1С разработчики регулярно сталкиваются с проблемой: в Postman запрос отрабатывает корректно и сессия авторизуется, а в 1С сервер при каждом запросе присылает новые заголовки Set-Cookie, а авторизация сбрасывается или возвращает ошибку.

В этой статье мы подробно разберем, почему возникает такое поведение, как устроена работа с сессиями на стороне веб-серверов, как правильно передавать заголовки Content-Type и Cookie в 1С, а также как обойти сложную защиту от межсайтовой подделки запросов (CSRF / AntiForgery).

1. Ошибка в заполнении заголовка Content-Type

Одной из наиболее частых причин, почему бэкенд сайта отказывается принимать POST-запрос с авторизационными данными из 1С, является некорректно заполненный заголовок Content-Type. Веб-сервер жестко ожидает данные в определенном формате (например, application/x-www-form-urlencoded или multipart/form-data), и если формат не совпадает или заголовок передан с синтаксической ошибкой, сервер просто проигнорирует переданное тело запроса.

Рассмотрим классическую ошибку заполнения заголовков в 1С, с которой часто сталкиваются начинающие разработчики:


// ОШИБОЧНЫЙ ВАРЯНТ: Попытка вставить структуру внутрь метода Вставить()
Заголовки = Новый Соответствие;
Заголовки.Вставить(Новый Структура("Парам, Знч", "Content-Type", "application/x-www-form-urlencoded;charset=utf-8"));

При таком подходе платформа 1С не сможет корректно интерпретировать заголовок, и он либо уйдет в искаженном виде, либо вообще не будет отправлен. Чтобы исправить эту ошибку, необходимо передавать имя заголовка и его значение как два простых строковых параметра метода Вставить() объекта Соответствие:


// ПРАВИЛЬНЫЙ ВАРИАНТ: Прямая передача строковых ключа и значения
Заголовки = Новый Соответствие;
Заголовки.Вставить("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");

Помните, что Postman и современные браузеры «интеллектуальны» — они автоматически подставляют правильный Content-Type на основе выбранного режима формирования тела запроса (например, при переключении вкладки Body в режим x-www-form-urlencoded). В 1С всю эту работу разработчику приходится делать вручную.

2. Безрежимность HTTP-соединения в 1С (Stateless)

Вторая и самая фундаментальная причина изменения кук — это природа работы HTTP-клиента в 1С. В браузере и Postman реализован механизм автоматического управления сессиями (так называемый Cookie Jar). Когда браузер выполняет первый GET-запрос к странице авторизации, сервер возвращает заголовки Set-Cookie. Браузер автоматически сохраняет их в свою внутреннюю базу данных и прикрепляет заголовок Cookie ко всем последующим запросам к этому домену.

Объект HTTPСоединение в 1С работает по принципу stateless (без сохранения состояния):

  1. Каждое создание нового HTTP-запроса через Новый HTTPЗапрос инициализирует абсолютно «чистый» объект, не знающий ничего о предыдущих вызовах.
  2. Даже если вы используете один и тот же объект HTTPСоединение для нескольких вызовов, куки автоматически не переносятся между запросами.
  3. Если сервер не получает обратно отправленную им ранее куку сессии (например, PHPSESSID или JSESSIONID), он считает каждый ваш запрос новым уникальным посетителем и генерирует новую куку сессии, возвращая её в очередном Set-Cookie.

Для сохранения авторизации мы должны самостоятельно извлекать куки из полученного ответа сервера и передавать их в заголовки следующего запроса.

3. Как правильно передавать куки из ответа в следующий запрос

Просто скопировать содержимое заголовка Set-Cookie из ответа в заголовок нового запроса не получится. Сервер возвращает куки со служебной информацией, которая выглядит следующим образом:


Set-Cookie: session_id=xyz123; path=/; domain=example.com; expires=Mon, 23-Jun-2026 12:00:00 GMT; HttpOnly; Secure

В заголовок Cookie исходящего запроса нужно передавать только пары Ключ=Значение, очищенные от системных параметров времени жизни (expires), пути (path), домена (domain) и флагов безопасности (HttpOnly, Secure). На сервер должна вернуться исключительно строка вида:

Cookie: session_id=xyz123

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

Cookie: session_id=xyz123; tracking_id=abc789

Рассмотрим пример кода, демонстрирующий базовый принцип ручного переноса кук в 1С:


// Выполняем первый запрос для получения кук сессии
Соединение = Новый HTTPСоединение("example.com", , , , , , Новый ЗащищенноеСоединениеOpenSSL());
ЗапросGET = Новый HTTPЗапрос("/auth/login");
ОтветGET = Соединение.Получить(ЗапросGET);

// Извлекаем заголовки ответа
ЗаголовкиОтвета = ОтветGET.Заголовки;
СтрокаSetCookie = ЗаголовкиОтвета.Получить("Set-Cookie");

Если СтрокаSetCookie <> Неопределено Тогда
    // Простейшая очистка куки (берем все до первой точки с запятой)
    ПозицияРазделителя = Найти(СтрокаSetCookie, ";");
    Если ПозицияРазделителя > 0 Тогда
        ЧистаяКука = Лев(СтрокаSetCookie, ПозицияРазделителя - 1);
    Иначе
        ЧистаяКука = СтрокаSetCookie;
    КонецЕсли;
    
    // Формируем следующий POST-запрос с авторизацией
    ЗапросPOST = Новый HTTPЗапрос("/auth/login");
    ЗапросPOST.Заголовки.Вставить("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");
    ЗапросPOST.Заголовки.Вставить("Cookie", ЧистаяКука);
    
    // Устанавливаем параметры авторизации в тело
    ТелоЗапроса = "login=myusername&password=mypassword";
    ЗапросPOST.УстановитьТелоИзСтроки(ТелоЗапроса);
    
    ОтветPOST = Соединение.ОтправитьДляОбработки(ЗапросPOST);
    // Проверяем результат авторизации...
КонецЕсли;

4. Защита AntiForgery и CSRF-токены

Если после настройки передачи кук и заголовка Content-Type вы все равно получаете ответ с кодом 200, но вместо авторизованной сессии сервер возвращает ошибку вроде "Status":"WrongAntiForgery" или пустой токен доступа, вы столкнулись с защитой от межсайтовой подделки запросов (CSRF/AntiForgery).

Этот механизм работает следующим образом:

  1. При первом GET-запросе к форме авторизации сервер генерирует уникальный случайный токен.
  2. Этот токен сервер отправляет клиенту одновременно в двух местах: записывает в заголовок Set-Cookie (например, под именем __RequestVerificationToken) и внедряет непосредственно в тело HTML-страницы (скрытое поле ввода формы <input type="hidden" name="__RequestVerificationToken" value="токен" />).
  3. При POST-запросе авторизации сервер ожидает получить оба значения. Он сверяет токен из тела POST-запроса (или специального заголовка) с токеном из куки сессии. Они связаны криптографическим ключом.
  4. Если в 1С вы извлекли токен из HTML-кода страницы и подставили его в тело POST-запроса, но **забыли отправить обратно соответствующую куку сессии** (или отправили её неочищенной), сервер вернет ошибку проверки AntiForgery-защиты.

Для успешной авторизации на таких ресурсах необходимо строго соблюдать двухэтапный сценарий:

5. Проблема парсинга сложных кук (Запятые в датах Expires)

Когда веб-сервер возвращает несколько заголовков Set-Cookie (например, одну куку для сессии, другую для защиты AntiForgery, третью для систем аналитики), платформа 1С объединяет их все в одну строку внутри соответствия HTTPОтвет.Заголовки под ключом "Set-Cookie", разделяя запятыми.

Здесь разработчиков подстерегает опасная «ловушка». Значение параметра expires содержит дату в формате HTTP, которая сама по себе содержит запятую после дня недели (например, expires=Mon, 23-Jun-2026 12:00:00 GMT). Если вы попытаетесь разделить строку кук стандартным методом СтрРазделить(СтрокаКук, ","), вы разобьете дату посередине, испортив структуру куки.

Разберем, как правильно распарсить сложную строку кук с помощью регулярных выражений, которые стали доступны в платформе 1С начиная с версии 8.3.23:


Функция ПолучитьОчищенныеКукиИзОтвета(HTTPОтвет) Экспорт
    
    СтрокаSetCookie = HTTPОтвет.Заголовки.Получить("Set-Cookie");
    Если ПустаяСтрока(СтрокаSetCookie) Тогда
        Возврат "";
    КонецЕсли;
    
    // Регулярное выражение для поиска запятых-разделителей между куками,
    // игнорируя запятые внутри параметров expires (после сокращений дней недели)
    // Шаблон ищет запятые, за которыми НЕ следует название дня недели в дате expires
    ШаблонРазделителя = ",(?!\s*(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun)\b)";
    
    МассивКук = Новый Массив;
    
    #Если Клиент Или Сервер Тогда
    // Используем встроенный механизм регулярных выражений 1С (с версии 8.3.23)
    // Для более старых версий платформы можно использовать COM-объект "VBScript.RegExp"
    ЗапускRegExp = Новый РегулярноеВыражение(ШаблонРазделителя, Истина);
    СтрокаРазделенная = ЗапускRegExp.Заменить(СтрокаSetCookie, "|");
    ЧастиКук = СтрРазделить(СтрокаРазделенная, "|");
    #КонецЕсли
    
    Для Каждого СтрокаКуки Из ЧастиКук Цикл
        СтрокаКуки = СокрЛП(СтрокаКуки);
        // Извлекаем только левую часть до первой точки с запятой
        ПозицияТочкиСЗапятой = Найти(СтрокаКуки, ";");
        Если ПозицияТочкиСЗапятой > 0 Тогда
            ОчищеннаяКука = Лев(СтрокаКуки, ПозицияТочкиСЗапятой - 1);
        Иначе
            ОчищеннаяКука = СтрокаКуки;
        КонецЕсли;
        
        Если Не ПустаяСтрока(ОчищеннаяКука) Тогда
            МассивКук.Добавить(ОчищеннаяКука);
        КонецЕсли;
    КонецЦикла;
    
    Возврат СтрСоединить(МассивКук, "; ");
    
КонецФункции

6. Альтернативный вариант: Использование COM-объекта WinHTTP

Если ваша база 1С работает под управлением операционной системы Windows и вам не хочется вручную писать сложные парсеры кук и обрабатывать редиректы, отличной альтернативой стандартному HTTPСоединение является использование системного COM-объекта WinHttp.WinHttpRequest.5.1.

Этот объект реализует полноценный веб-клиент на уровне ОС Windows, который умеет автоматически поддерживать сессию, переносить куки между последовательными запросами и самостоятельно обрабатывать перенаправления (редиректы 3xx):


// Инициализируем COM-объект (сессия будет сохраняться на протяжении жизни объекта)
WinHTTP = Новый COMОбъект("WinHttp.WinHttpRequest.5.1");

// Шаг 1: Получаем страницу авторизации (WinHTTP сохранит куки во внутреннем кэше)
WinHTTP.Open("GET", "https://example.com/auth/login", Ложь);
WinHTTP.Send();

// Шаг 2: Выполняем POST-запрос (WinHTTP автоматически прикрепит куки из первого шага)
WinHTTP.Open("POST", "https://example.com/auth/login", Ложь);
WinHTTP.SetRequestHeader("Content-Type", "application/x-www-form-urlencoded");

// Передаем параметры авторизации
ПараметрыАвторизации = "login=myusername&password=mypassword";
WinHTTP.Send(ПараметрыАвторизации);

// Проверяем статус ответа
КодСостояния = WinHTTP.Status;
Если КодСостояния = 200 Тогда
    ТелоОтвета = WinHTTP.ResponseText;
    // Мы успешно авторизовались! Дальнейшие запросы через объект WinHTTP продолжат использовать эту сессию.
КонецЕсли;

Использование WinHttpRequest значительно упрощает разработку парсеров и механизмов авторизации на сложных сайтах, минимизируя необходимость ручного контроля сетевого уровня.

← На главную