При интеграции 1С с внешними веб-сервисами и API часто возникает сценарий, когда первым запросом необходимо пройти аутентификацию (например, передав логин и пароль), а последующими обращениями получать или отправлять прикладные данные. Разработчики нередко сталкиваются с ситуацией: первый запрос авторизации завершается успешно с кодом состояния 200, однако следующий же запрос к защищенному разделу API возвращает ошибку 401 Unauthorized, будто авторизация вовсе не выполнялась.
Разберем по шагам, почему так происходит, как устроена работа с состоянием в платформе 1С и как правильно реализовать передачу сессионных cookie.
Выясним причину такого поведения. В отличие от стандартных интернет-браузеров, платформа 1С и ее объект HTTPСоединение не обладают встроенным механизмом автоматического сохранения состояния (Cookie Jar). Сам протокол HTTP по своей природе является протоколом без сохранения состояния (stateless).
Когда мы выполняем авторизацию в браузере, сервер присылает специальный заголовок Set-Cookie, содержащий идентификатор сессии. Браузер перехватывает этот заголовок, сохраняет cookie в локальное хранилище и при каждом последующем обращении к этому домену автоматически прикрепляет заголовок Cookie к исходящему запросу.
В платформе 1С объект HTTPСоединение отвечает исключительно за установление сетевого транспорта. Вся логика сохранения, парсинга и подстановки кук в каждый последующий объект HTTPЗапрос возлагается непосредственно на разработчика. Если не передать полученный идентификатор сессии во второй запрос явно, веб-сервер рассматривает входящее соединение как новое и анонимное.
Кроме того, распространенной ошибкой является попытка передать заголовки третьим параметром в метод Получить() или ОтправитьДляОбработки() объекта HTTPСоединение. Заголовки HTTP-пакета должны передаваться непосредственно в конструктор самого объекта HTTPЗапрос или устанавливаться через его свойство Заголовки.
Рассмотрим, как правильно извлечь данные сессии после успешной авторизации и передать их в следующий запрос.
Set-Cookie из коллекции HTTPОтвет.Заголовки.Соответствие, в которую помещаем ключ Cookie со значением полученной сессии.HTTPЗапрос.Посмотрим на практический пример реализации такого взаимодействия:
// 1. Инициализируем соединение с сервером
HTTPСоединение = Новый HTTPСоединение("api.myshows.ru");
// 2. Отправляем запрос авторизации
HTTPЗапросАвторизации = Новый HTTPЗапрос("profile/login?login=" + Логин + "&password=" + ХешПароля);
ОтветАвторизации = HTTPСоединение.Получить(HTTPЗапросАвторизации);
Если ОтветАвторизации.КодСостояния = 200 Тогда
// 3. Получаем сессионные куки из заголовков ответа
СтрокаКуки = ОтветАвторизации.Заголовки.Получить("Set-Cookie");
// 4. Подготавливаем заголовки для следующего запроса к закрытому API
ЗаголовкиЗапроса = Новый Соответствие();
ЗаголовкиЗапроса.Вставить("Cookie", СтрокаКуки);
ЗаголовкиЗапроса.Вставить("Connection", "keep-alive");
// 5. Передаем заголовки в конструктор HTTPЗапрос вторым параметром
HTTPЗапросДанных = Новый HTTPЗапрос("profile/", ЗаголовкиЗапроса);
ОтветДанных = HTTPСоединение.Получить(HTTPЗапросДанных);
Сообщить("Код ответа защищенного ресурса: " + ОтветДанных.КодСостояния);
Иначе
Сообщить("Ошибка авторизации. Код состояния: " + ОтветАвторизации.КодСостояния);
КонецЕсли;
Рассмотрим подробнее технические ограничения, о которых необходимо помнить при интеграции со сложными веб-сервисами:
Set-Cookie от сервера часто содержит служебные параметры: path=/; domain=.site.ru; HttpOnly; Secure; Expires=.... Стандарт протокола HTTP требует, чтобы в исходящем запросе передавались только пары вида Имя=Значение; Имя2=Значение2. Некоторые веб-серверы лояльны и могут принять всю строку целиком, однако строгие шлюзы безопасности (WAF) отклонят такой запрос. В промышленных решениях рекомендуется выделять регулярным выражением или строковыми функциями только саму пару имени и значения cookie до первого символа точки с запятой.Set-Cookie, платформа 1С объединяет их в одно строковое значение через запятую. Поскольку дата истечения срока (Expires) также содержит запятую в англоязычном формате даты, разделять строку простым методом СтрРазделить() по символу запятой нельзя.HTTPСоединение необходимо передавать порт 443 и настроенный объект ЗащищенноеСоединениеOpenSSL.КодироватьСтроку(Значение, СпособКодированияСтроки.КодировкаURL) для защиты от искажения данных спецсимволами.После того как авторизованный запрос успешно выполнен и сервер вернул код состояния 200, прикладные данные обычно передаются в теле ответа в формате JSON. Разберем по шагам, как обработать ответ встроенными средствами 1С:
Если ОтветДанных.КодСостояния = 200 Тогда
// Считываем тело ответа в виде строки с указанием кодировки
СтрокаJSON = ОтветДанных.ПолучитьТелоКакСтроку("UTF-8");
// Инициализируем парсер JSON
ЧтениеJSON = Новый ЧтениеJSON();
ЧтениеJSON.УстановитьСтроку(СтрокаJSON);
// Десериализуем данные в структуры и массивы 1С
ДанныеОтвета = ПрочитатьJSON(ЧтениеJSON);
ЧтениеJSON.Закрыть();
// Работаем с данными как с обычной структурой
// Например: Сообщить(ДанныеОтвета.login);
КонецЕсли;
Если объем получаемых от API данных исчисляется десятками мегабайт, для экономии оперативной памяти сервера 1С вместо чтения всей строки целиком можно использовать потоковую обработку через метод ОтветДанных.ПолучитьТелоКакПоток() и метод ЧтениеJSON.ОткрытьПоток().
Проанализируем альтернативный вариант, если разбор сложных составных cookie и редиректов средствами 1С оказывается слишком трудоемким. При работе на клиенте под управлением операционной системы Windows или на Windows-сервере можно применить системный COM-объект WinHttp.WinHttpRequest.5.1.
Преимущество этого компонента заключается в том, что операционная система берет управление сессиями на себя. Экземпляр объекта WinHttp автоматически сохраняет все полученные cookies и подставляет их в последующие вызовы в рамках одного скрипта без необходимости парсить строки вручную:
Попытка
HTTPЗапросCOM = Новый COMОбъект("WinHttp.WinHttpRequest.5.1");
// 1. Авторизация
HTTPЗапросCOM.Open("GET", "https://api.myshows.ru/profile/login?login=" + Логин + "&password=" + ХешПароля, Ложь);
HTTPЗапросCOM.Send();
Если HTTPЗапросCOM.Status = 200 Тогда
// 2. Запрос данных - куки подставятся операционной системой автоматически
HTTPЗапросCOM.Open("GET", "https://api.myshows.ru/profile/", Ложь);
HTTPЗапросCOM.Send();
ТекстОтвета = HTTPЗапросCOM.ResponseText;
Сообщить("Статус запроса: " + HTTPЗапросCOM.Status);
КонецЕсли;
Исключение
Сообщить("Ошибка вызова WinHttp: " + ОписаниеОшибки());
КонецПопытки;
Однако помните, что вызов COM-объектов платформозависим и не будет работать на серверах под управлением Linux, поэтому для кроссплатформенных конфигураций приоритетным остается использование штатных объектов HTTPСоединение и HTTPЗапрос с ручной передачей заголовка Cookie.