SOAP-сервисы с предварительной Cookie-аутентификацией

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

Рассмотрим постановку задачи и её решение.

Задача:

Есть soap-сервис крупного поставщика DHL, построенный на ASP.NET. При обращении к сервису веб-сервер поставщика производит 302-редирект на страницу аутентификации:

После ввода логина и пароля открывается wsdl-описание.

Требуется организовать работу с методами данного веб-сервиса из кода 1С.

Типовые объекты WSОпределение и WSПрокси с данным веб-сервисом работать не могут, поскольку не имеют возможности указания произвольных заголовков HTTP-запроса для передачи значения cookie-аутентификации.

Решение:

План таков:

1. Получим страницу аутентификации GET-запросом и сохраним предварительно устанавливаемое в cookie случайное значение. Это значение впоследствии используется для идентификации сеанса.

2. POST-запросом проведём отправку данных формы аутентификации. В ответе сервера получим cookie-аутентификации.

3. С методами веб-сервиса будем работать с помощью POST-запросов, устанавливая полученные cookie.

4. Чтобы упростить сборку требуемых для методов сервиса soap-запросов, воспользуемся XDTO-описанием параметров и результатов методов, полученным от поставщика.

Пример SOAP-запроса и ответа:

Запрос:

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<GetPtsList xmlns="http://tempuri.org/">
<auth>
<Login>string</Login>
<Password>string</Password>
</auth>
<startDate>dateTime</startDate>
<endDate>dateTime</endDate>
</GetPtsList>
</soap:Body>
</soap:Envelope>

Ответ:

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<GetPtsListResponse xmlns="http://tempuri.org/">
<GetPtsListResult>
<PtsItemInfo>
<DealerReceiveDate>dateTime</DealerReceiveDate>
<PickupDate>dateTime</PickupDate>
<ReferenceNumber>string</ReferenceNumber>
<RegistrationDate>dateTime</RegistrationDate>
<PtsNumber>string</PtsNumber>
<VinNumber>string</VinNumber>
<DhlStation>string</DhlStation>
<DestinationDhlStation>string</DestinationDhlStation>
<Dealer>string</Dealer>
<Model>string</Model>
<Gtd>string</Gtd>
<Brand>string</Brand>
<PtsStatus>string</PtsStatus>
<BrandCode>string</BrandCode>
<DealerCode>string</DealerCode>
<Awb>string</Awb>
</PtsItemInfo>
</GetPtsListResult>
</GetPtsListResponse>
</soap:Body>
</soap:Envelope>

Для удобной генерации XML запроса и ответа создадим XDTO-пакет с описанием объектов GetPtsListauthGetPtsListResponseGetPtsListResultPtsItemInfo с пространством имён http://tempuri.org/ .

Реализация:

1. Получение страницы аутентификации GET-запросом и отправка данных формы (username, password):

Функция АвторизацияНаВебСервере() Экспорт

СтруктураСоединение = Новый Структура("Результат, HTTPСоединение, Заголовки, НастройкиПодключения");
СтруктураСоединение.Результат = Ложь;

// из регистра сведений получаем сервер, url, логин, пароль для подключения
НастройкиПодключения = ПолучитьНастройкиПодключения();

Если НастройкиПодключения.Количество()=0 Тогда
Возврат СтруктураСоединение;
КонецЕсли;

НастройкаПодключения = НастройкиПодключения[0];

АдресСервера = СокрЛП(НастройкаПодключения.ДопПараметр1);
АдресАвторизации = СокрЛП(НастройкаПодключения.ДопПараметр2);

UserName = СокрЛП(НастройкаПодключения.Пользователь);
Password = СокрЛП(НастройкаПодключения.Пароль);

// данные авторизации - значения параметров формы авторизации, их можно подсмотреть с помощью, например, Advanced REST client
ДанныеАвторизации = "&UserName="+UserName+"&Password="+Password+"&RememberMe=true&CustomAuthKey=";

Попытка
Соединение = Новый HTTPСоединение(АдресСервера);

// получим страницу GET-запросом, в Заголовки0 вернутся предварительные cookie
Заголовки0 = Новый Соответствие;
Результат0 = ВыполнитьGETЗапрос(Соединение, АдресАвторизации, Заголовки0);

Если Результат0.Ответ.КодСостояния<>200 Тогда
Возврат СтруктураСоединение;
КонецЕсли;

// отправим POST-запросом данные формы для аутентификации, в Заголовки1 вернутся новые cookie,
// уже со значением успешной аутентификации. Эти новые cookie мы сохраним и будем использовать
// при выполнении методов сервиса
Заголовки1 = Новый Соответствие;
Заголовки1.Вставить("Cookie",Результат0.Ответ.Заголовки["Set-Cookie"]);
Заголовки1.Вставить("Content-Type", "application/x-www-form-urlencoded");

Результат1 = ВыполнитьPOSTЗапрос(Соединение,АдресАвторизации,Заголовки1,ДанныеАвторизации);

Если Результат1.Ответ.КодСостояния<>302  Тогда
Возврат СтруктураСоединение;
КонецЕсли;
СтруктураСоединение.HTTPСоединение = Соединение;
СтруктураСоединение.Заголовки = Новый Соответствие;

// в Результат1.Ответ.Заголовки["Set-Cookie"] находится заветный ключ
СтруктураСоединение.Заголовки.Вставить("Cookie",Результат1.Ответ.Заголовки["Set-Cookie"]);
СтруктураСоединение.Результат = Истина;
СтруктураСоединение.НастройкиПодключения = НастройкаПодключения;

Исключение
ЗаписьЖурналаРегистрации("ИнтеграцияСDHL.АвторизацияНаВебСервере",
УровеньЖурналаРегистрации.Ошибка,
,,ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
КонецПопытки;

Возврат СтруктураСоединение;

КонецФункции

2. Сборка soap-запроса, его выполнение посредством POST-запроса и получение результата в XDTO:

Функция GetMethodName(СтруктураПараметров) Экспорт

ОбъектXDTO = Неопределено;

СтруктураСоединение = АвторизацияНаВебСервере();
Если НЕ СтруктураСоединение.Результат Тогда
Возврат ОбъектXDTO;
КонецЕсли;

ПутьКМетодуСервиса = "urlpart1/Service.asmx?op=GetMethodName";

// предварительно в метаданных опишем XDTO-пакеты для параметров сервиса и для возвращаемых значений.
ТипGetPtsList = ФабрикаXDTO.Тип("http://tempuri.org/","GetPtsList");
Типauth = ФабрикаXDTO.Тип("http://tempuri.org/","auth");

ПараметрXDTO = ФабрикаXDTO.Создать(ТипGetPtsList);
ПараметрXDTO.startDate = СтруктураПараметров.startDate;
ПараметрXDTO.endDate = СтруктураПараметров.endDate;
Параметрauth = ФабрикаXDTO.Создать(Типauth);
Параметрauth.Login = СтруктураСоединение.НастройкиПодключения.Пользователь;
Параметрauth.Password = СтруктураСоединение.НастройкиПодключения.Пароль;
ПараметрXDTO.auth = Параметрauth;

Запись = Новый ЗаписьXML;
Запись.УстановитьСтроку();

// генерируем XML для параметров метода сервиса
ФабрикаXDTO.ЗаписатьXML(Запись,ПараметрXDTO);

СтрXML = Запись.Закрыть();

// для указания нужных пространств имён в xml-запросе и ответе применена быстрая и грубая замена подстрок
СтрXML = СтрЗаменить(СтрXML," xmlns:xs=""http://www.w3.org/2001/XMLSchema"" xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance""","");

SOAPЗапрос = "<?xml version=""1.0"" encoding=""utf-8""?>
|<soap:Envelope xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns:xsd=""http://www.w3.org/2001/XMLSchema"" xmlns:soap=""http://schemas.xmlsoap.org/soap/envelope/"">
|  <soap:Body>"
+СтрXML+
"</soap:Body>
|</soap:Envelope>";

ФайлЗапроса = ПолучитьИмяВременногоФайла();
ТекстовыйФайл = Новый ТекстовыйДокумент;
ТекстовыйФайл.УстановитьТекст(SOAPЗапрос);
ТекстовыйФайл.Записать(ФайлЗапроса,КодировкаТекста.ANSI);

ФайлРезультата = ПолучитьИмяВременногоФайла();

ЗаголовокНТТР = Новый Соответствие;
ЗаголовокНТТР.Вставить("Cookie",СтруктураСоединение.Заголовки["Cookie"]);

ФайлОтправки = Новый Файл(ФайлЗапроса);
РазмерФайлаОтправки = XMLСтрока(ФайлОтправки.Размер());

ЗаголовокНТТР.Вставить("Content-Type", "text/xml; charset=utf-8");
ЗаголовокНТТР.Вставить("Content-Lenght", РазмерФайлаОтправки);

СтруктураСоединение.HTTPСоединение.ОтправитьДляОбработки(ФайлЗапроса,ПутьКМетодуСервиса,ФайлРезультата,ЗаголовокНТТР);

// Получаем ответ в виде строки

ТекстовыйФайлОтвет = Новый ТекстовыйДокумент;
ТекстовыйФайлОтвет.Прочитать(ФайлРезультата,КодировкаТекста.UTF8);
СтрокаРезультат = ТекстовыйФайлОтвет.ПолучитьТекст();

// постобработка xml:
СтрокаРезультат = СтрЗаменить(СтрокаРезультат,"<soap:Envelope xmlns:soap=""http://schemas.xmlsoap.org/soap/envelope/"" xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns:xsd=""http://www.w3.org/2001/XMLSchema""><soap:Body>","");
СтрокаРезультат = СтрЗаменить(СтрокаРезультат,"</soap:Body></soap:Envelope>","");
СтрокаРезультат = СтрЗаменить(СтрокаРезультат, "xmlns=""http://tempuri.org/""","xmlns=""http://tempuri.org/"" xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns:xs=""http://www.w3.org/2001/XMLSchema""");

Попытка
ТипGetPtsListResponse = ФабрикаXDTO.Тип("http://tempuri.org/","GetPtsListResponse");

Чтение = Новый ЧтениеXML;
Чтение.УстановитьСтроку(СтрокаРезультат);
ОбъектXDTO = ФабрикаXDTO.ПрочитатьXML(Чтение,ТипGetPtsListResponse);
Исключение
ЗаписьЖурналаРегистрации("GetMethodName",
УровеньЖурналаРегистрации.Ошибка,
,,ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
КонецПопытки;

// уборка
Попытка
УдалитьФайлы(ФайлЗапроса);
УдалитьФайлы(ФайлРезультата);
Исключение

КонецПопытки;

Возврат ОбъектXDTO;

КонецФункции

Реализация функция GET-запроса и POST-запроса:

// Выполняет прозвольный POST-запрос
Функция ВыполнитьPOSTЗапрос(Соединение, АдресРесурса, ЗаголовкиЗапроса, ДанныеЗапроса) Экспорт

СтруктураРезультат = Новый Структура;

Запрос = Новый HTTPЗапрос(АдресРесурса,ЗаголовкиЗапроса);

Запрос.УстановитьТелоИзСтроки(ДанныеЗапроса);
Ответ = Соединение.ОтправитьДляОбработки(Запрос);
ОтветВВидеСтроки = Ответ.ПолучитьТелоКакСтроку("UTF-8");

СтруктураРезультат.Вставить("Ответ",Ответ);
СтруктураРезультат.Вставить("ТекстОтвета",ОтветВВидеСтроки);

Возврат СтруктураРезультат;

КонецФункции

// Выполняет произвольный GET-запрос
Функция ВыполнитьGETЗапрос(Соединение, АдресРесурса, ЗаголовкиЗапроса) Экспорт

СтруктураРезультат = Новый Структура;

ИмяВыходногоФайлаGET = ПолучитьИмяВременногоФайла("txt");
HTTPЗапросGET = Новый HTTPЗапрос(АдресРесурса,ЗаголовкиЗапроса);
HTTPОтветGET = Соединение.Получить(HTTPЗапросGET,ИмяВыходногоФайлаGET);

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

ЧтениеФайла = Новый ТекстовыйДокумент;
ЧтениеФайла.Прочитать(ИмяВыходногоФайлаGET);

ТекстОтвета = ЧтениеФайла.ПолучитьТекст();

СтруктураРезультат.Вставить("Ответ",HTTPОтветGET);
СтруктураРезультат.Вставить("ТекстОтвета",ТекстОтвета);

Попытка
УдалитьФайлы(ИмяВыходногоФайлаGET);
Исключение

КонецПопытки;

Возврат СтруктураРезультат;

КонецФункции

В результате получилось использовать методы soap-сервисов поставщика с предварительной аутентфикацией посредством cookie.

Скорее всего, можно сделать более красивой сборку текста xml-запроса, но это уже в следующей статье!

Leave a Comment

Ваш адрес email не будет опубликован. Обязательные поля помечены *