gifts2017

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

Опубликовал Mike Scherbakov (blackschool) в раздел Обмен - Интеграция с WEB

Платформа 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-запроса, но это уже в следующей статье!

См. также

Подписаться Добавить вознаграждение
В этой теме еще нет сообщений.
Для написания сообщения необходимо авторизоваться
Прикрепить файл
Дополнительные параметры ответа