Как реализовать подключение к SOAP веб-сервису с защитой WS-Security в 1С?

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

При интеграции 1С:Предприятия с современными корпоративными или государственными веб-сервисами разработчики часто сталкиваются с требованием поддержки стандарта безопасности WS-Security (WSS). Попытка решить эту задачу стандартными средствами платформы, такими как объект WSПрокси, как правило, заканчивается неудачей. Платформа 1С не умеет «из коробки» настраивать сложные параметры заголовков авторизации безопасности (например, тип авторизации, шифрование паролей, передача цифровых подписей в SOAP-конверте).

В этой статье мы подробно разберем, почему стандартный WSПрокси не подходит для решения этой задачи, и по шагам рассмотрим рабочие альтернативные варианты: от ручного формирования SOAP-сообщений через HTTPСоединение до программного расчета хеша PasswordDigest и генерации структуры цифровой подписи (XMLDSIG).

Почему стандартный WSПрокси не подходит для WS-Security

Объекты платформы WSОпределения и WSПрокси изначально создавались для работы с простыми сценариями авторизации — такими как HTTP Basic Auth (передача логина и пароля в заголовке транспортного уровня) или обеспечение безопасности на уровне транспорта (SSL/TLS с клиентскими сертификатами). Однако спецификация WS-Security работает на уровне самого SOAP-сообщения, внедряя учетные данные прямо в XML-заголовок (тег <soap:Header>).

Стандартный WSПрокси скрывает от разработчика структуру XML-пакета и не предоставляет низкоуровневого доступа к его заголовкам. Нам банально негде указать тип авторизации, передать временную метку (Created), соль (Nonce) или зашифровать пароль методом PasswordDigest. Именно поэтому для работы с WS-Security необходимо перейти к ручному формированию SOAP-пакетов и их отправке через HTTPСоединение.

Шаг 1. Реализация обмена через HTTPСоединение

Давайте рассмотрим базовый шаблон отправки SOAP-запроса с WS-Security авторизацией. Вместо использования WSПрокси мы конструируем XML-тело запроса вручную и отправляем его как текстовое содержимое POST-запроса. Проанализируем рабочий пример подключения к защищенному веб-сервису Oracle Cloud:


// Настраиваем защищенное соединение SSL
ЗащищенноеСоединение = Новый ЗащищенноеСоединениеOpenSSL(
    Новый СертификатКлиентаWindows, 
    Новый СертификатыУдостоверяющихЦентровWindows
);

// Устанавливаем HTTP-соединение с хостом веб-сервиса
HTTPСоединение = Новый HTTPСоединение("hckj.bi.em2.oraclecloud.com", 443, , , , , ЗащищенноеСоединение);

// Создаем HTTP-запрос к конкретной точке подключения (endpoint)
HTTPЗапрос = Новый HTTPЗапрос("/xmlpserver/services");
HTTPЗапрос.Заголовки.Вставить("Content-Type", "application/soap+xml;charset=UTF-8");
HTTPЗапрос.Заголовки.Вставить("SOAPAction", "");
HTTPЗапрос.Заголовки.Вставить("User-Agent", "Apache-HttpClient/4.1.1 (java 1.5)");
HTTPЗапрос.Заголовки.Вставить("Connection", "Keep-Alive");

// Шаблон SOAP-конверта с параметрами запроса
ШаблонXML = 
"<soapenv:Envelope xmlns:soapenv=""http://schemas.xmlsoap.org/soap/envelope/"" xmlns:pub=""http://xmlns.oracle.com/oxp/service/PublicReportService"">
|   <soapenv:Header>
|       <wsse:Security xmlns:wsse=""http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"">
|           <wsse:UsernameToken>
|               <wsse:Username>ИмяПользователя</wsse:Username>
|               <wsse:Password Type=""http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText"">Пароль</wsse:Password>
|           </wsse:UsernameToken>
|       </wsse:Security>
|   </soapenv:Header>
|   <soapenv:Body>
|       <pub:runReport>
|           <pub:reportRequest>
|               <pub:attributeFormat>csv</pub:attributeFormat>
|               <pub:reportAbsolutePath>/Custom/Services/1C/Report.xdo</pub:reportAbsolutePath>
|               <pub:sizeOfDataChunkDownload>-1</pub:sizeOfDataChunkDownload>
|           </pub:reportRequest>
|       </pub:runReport>
|   </soapenv:Body>
|</soapenv:Envelope>";

// Заполняем тело запроса
HTTPЗапрос.УстановитьТелоИзСтроки(ШаблонXML, "utf-8", ИспользованиеByteOrderMark.НеИспользовать);

// Отправляем данные на сервер и получаем ответ
Попытка
    HTTPОтвет = HTTPСоединение.ОтправитьДляОбработки(HTTPЗапрос);
    Если HTTPОтвет.КодСостояния = 200 Тогда
        Сообщить("Запрос успешно выполнен!");
        СодержимоеОтвета = HTTPОтвет.ПолучитьТелоКакСтроку();
        // Далее обрабатываем полученный XML через ЧтениеXML или ПостроительDOM
    Иначе
        Сообщить("Ошибка сервера. Код состояния: " + HTTPОтвет.КодСостояния);
        Сообщить(HTTPОтвет.ПолучитьТелоКакСтроку());
    КонецЕсли;
Исключение
    Сообщить("Не удалось подключиться к сервису. Описание ошибки: " + ОписаниеОшибки());
КонецПопытки;

В данном примере мы использовали самый простой тип токена безопасности — PasswordText, при котором пароль передается в открытом (но защищенном SSL-транспортом) виде. Однако большинство современных систем требуют более надежный вариант шифрования — PasswordDigest.

Шаг 2. Реализация шифрования PasswordDigest

Параметр PasswordDigest — это криптографический хеш, который генерируется динамически для каждого запроса. Это исключает возможность перехвата и повторного использования пароля злоумышленниками. Формула расчета хеша согласно спецификации OASIS WSS выглядит так:

PasswordDigest = Base64( SHA-1( Nonce + Created + Password ) )

Где:

  1. Nonce — случайная 16-байтовая последовательность (соль), генерируемая клиентом для каждого запроса. В XML-запрос она вставляется в кодировке Base64, но для расчета хеша должны использоваться ее исходные «сырые» байты.
  2. Created — текущее время в формате UTC (стандарт ISO 8601, например: 2026-10-27T12:00:00Z), преобразованное в массив байтов UTF-8.
  3. Password — строка вашего пароля, преобразованная в байты UTF-8.

Важный нюанс для разработчика 1С: простая строковая конкатенация этих трех параметров перед хешированием даст неверный результат! Нам необходимо выполнить слияние данных строго на уровне байтовых потоков. Выясним, как это реализовать средствами 1С:


Функция СформироватьWSSecurityDigest(Пользователь, Пароль) Экспорт
    // Генерация времени создания в формате UTC ISO 8601
    ТекущееВремяUTC = ТекущаяДата() - МестноеВремя(ТекущаяДата()) + '19700101000000';
    СтрокаCreated = Формат(ТекущееВремяUTC, "ДФ=yyyy-MM-ddTHH:mm:ssZ");
    
    // Генерируем случайную соль (Nonce) длиной 16 байт
    // Для примера сгенерируем фиксированный массив или воспользуемся криптографическим генератором
    // В реальной практике лучше использовать внешнюю компоненту или .NET для надежного крипто-рандома
    ДанныеСлучайные = Новый ДвоичныеДанные("1234567890123456"); // Упрощенный пример 16 байт
    СтрокаNonceBase64 = Base64Строка(ДанныеСлучайные);
    
    // Преобразуем компоненты в двоичные данные
    ДвоичныеДанныеCreated = ПолучитьДвоичныеДанныеИзСтроки(СтрокаCreated, КодировкаТекста.UTF8);
    ДвоичныеДанныеPassword = ПолучитьДвоичныеДанныеИзСтроки(Пароль, КодировкаТекста.UTF8);
    
    // Конкатенируем байты с помощью потока в памяти
    Поток = Новый ПотокВПамяти;
    ЗаписьДанных = Новый ЗаписьДанных(Поток);
    ЗаписьДанных.Записать(ДанныеСлучайные); // Первыми идут сырые байты Nonce
    ЗаписьДанных.Записать(ДвоичныеДанныеCreated);
    ЗаписьДанных.Записать(ДвоичныеДанныеPassword);
    ЗаписьДанных.Закрыть();
    
    СырыеДанныеДляХеша = Поток.ЗакрытьИПолучитьДвоичныеДанные();
    
    // Вычисляем SHA-1 хеш
    Хеширование = Новый ХешированиеДанных(ХешФункция.SHA1);
    Хеширование.Добавить(СырыеДанныеДляХеша);
    ДвоичныйХеш = Хеширование.ХешСумма;
    
    // Кодируем результат SHA-1 в формат Base64
    СтрокаPasswordDigest = Base64Строка(ДвоичныйХеш);
    
    // Собираем XML-заголовок безопасности
    ШаблонЗаголовка = 
    "<wsse:Security xmlns:wsse=""http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"" xmlns:wsu=""http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"">
    |   <wsse:UsernameToken wsu:Id=""UsernameToken-1"">
    |       <wsse:Username>" + Пользователь + "</wsse:Username>
    |       <wsse:Password Type=""http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest"">" + СтрокаPasswordDigest + "</wsse:Password>
    |       <wsse:Nonce EncodingType=""http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary"">" + СтрокаNonceBase64 + "</wsse:Nonce>
    |       <wsu:Created>" + СтрокаCreated + "</wsu:Created>
    |   </wsse:UsernameToken>
    |</wsse:Security>";
    
    Возврат ШаблонЗаголовка;
КонецФункции

Шаг 3. Формирование подписи XMLDSIG для СМЭВ и Госуслуг

Для взаимодействия с государственными системами (например, СМЭВ, ГИС ЖКХ, Госуслуги) спецификация WS-Security требует не просто логин и пароль, а подписание тела сообщения цифровой подписью (стандарт XMLDSIG). Рассмотрим пример формирования XML-структуры заголовка безопасности с использованием объектной модели DOM 1С:


Функция СоздатьСтруктуруWSSecurityДляSOAPсообщения(СтрокаXML) Экспорт
    ИД = Строка(Новый УникальныйИдентификатор);
    ДанныеИД = "body";
    
    ЧтениеXML = Новый ЧтениеXML;
    ЧтениеXML.УстановитьСтроку(СтрокаXML);
    ПостроительDOM = Новый ПостроительDOM;
    ДокументDOM = ПостроительDOM.Прочитать(ЧтениеXML);
    Корень = ДокументDOM.ЭлементДокумента;
    ПрефиксСОАП = Корень.НайтиПрефикс("http://schemas.xmlsoap.org/soap/envelope/");
    
    // Добавим пространство имен "wsse" для спецификации WS-Security
    ПространствоИменСекрет = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";
    ПрефиксСекрет = "wsse";
    УзелDOM = ДокументDOM.СоздатьАтрибут("xmlns:" + ПрефиксСекрет);
    УзелDOM.Значение = ПространствоИменСекрет;
    Корень.Атрибуты.УстановитьИменованныйЭлемент(УзелDOM);
    
    // Добавим пространство имен "wsu" для спецификации WS-Security Utility
    ПространствоИменУтилити = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd";
    ПрефиксУтилити = "wsu";
    УзелDOM = ДокументDOM.СоздатьАтрибут("xmlns:" + ПрефиксУтилити);
    УзелDOM.Значение = ПространствоИменУтилити;
    Корень.Атрибуты.УстановитьИменованныйЭлемент(УзелDOM);
    
    // URI пространства имен спецификации XML Signature
    ПространствоИменЭП = "http://www.w3.org/2000/09/xmldsig#";
    
    // Работа с Header
    Header = Корень.ПолучитьЭлементыПоИмени(ПрефиксСОАП, "Header")[0];
    
    // Создаем узел Security
    ЭлементDOM = ДокументDOM.СоздатьЭлемент(ПространствоИменСекрет, ПрефиксСекрет + ":Security");
    УзелDOM = ДокументDOM.СоздатьАтрибут(ПрефиксСОАП + ":mustUnderstand");
    УзелDOM.Значение = "1";
    ЭлементDOM.УстановитьУзелАтрибута(УзелDOM);
    Security = Header.ДобавитьДочерний(ЭлементDOM);
    
    // BinarySecurityToken - хранилище открытого ключа в Base64
    ЭлементDOM = ДокументDOM.СоздатьЭлемент(ПространствоИменСекрет, ПрефиксСекрет + ":BinarySecurityToken");
    ТекстDOM = ДокументDOM.СоздатьТекстовыйУзел("#Certificate_ENCODE_BASE64"); // Сюда подставится сертификат
    ЭлементDOM.ДобавитьДочерний(ТекстDOM);
    
    УзелDOM = ДокументDOM.СоздатьАтрибут(ПространствоИменУтилити, ПрефиксУтилити + ":Id");
    УзелDOM.Значение = ИД;
    ЭлементDOM.УстановитьУзелАтрибута(УзелDOM);
    
    УзелDOM = ДокументDOM.СоздатьАтрибут("ValueType");
    УзелDOM.Значение = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3";
    ЭлементDOM.УстановитьУзелАтрибута(УзелDOM);
    
    УзелDOM = ДокументDOM.СоздатьАтрибут("EncodingType");
    УзелDOM.Значение = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary";
    ЭлементDOM.УстановитьУзелАтрибута(УзелDOM);
    Security.ДобавитьДочерний(ЭлементDOM);
    
    // Signature - узел структуры подписи
    ЭлементDOM = ДокументDOM.СоздатьЭлемент(ПространствоИменЭП, "Signature");
    Signature = Security.ДобавитьДочерний(ЭлементDOM);
    
    // SignedInfo - информация о том, что именно подписывается
    SignedInfo = Signature.ДобавитьДочерний(ДокументDOM.СоздатьЭлемент(ПространствоИменЭП, "SignedInfo"));
    
    // Метод канонизации XML (c14n)
    CanonicalizationMethod = ДокументDOM.СоздатьЭлемент(ПространствоИменЭП, "CanonicalizationMethod");
    УзелDOM = ДокументDOM.СоздатьАтрибут("Algorithm");
    УзелDOM.Значение = "http://www.w3.org/2001/10/xml-exc-c14n#";
    CanonicalizationMethod.УстановитьУзелАтрибута(УзелDOM);
    SignedInfo.ДобавитьДочерний(CanonicalizationMethod);
    
    // Алгоритм подписи (например, ГОСТ или RSA)
    SignatureMethod = ДокументDOM.СоздатьЭлемент(ПространствоИменЭП, "SignatureMethod");
    УзелDOM = ДокументDOM.СоздатьАтрибут("Algorithm");
    УзелDOM.Значение = "http://www.w3.org/2001/04/xmldsig-more#gost34102001-gost3411";
    SignatureMethod.УстановитьУзелАтрибута(УзелDOM);
    SignedInfo.ДобавитьДочерний(SignatureMethod);
    
    // Ссылка на подписываемый объект (в данном случае - Body)
    Reference = ДокументDOM.СоздатьЭлемент(ПространствоИменЭП, "Reference");
    УзелDOM = ДокументDOM.СоздатьАтрибут("URI");
    УзелDOM.Значение = "#" + ДанныеИД;
    Reference.УстановитьУзелАтрибута(УзелDOM);
    SignedInfo.ДобавитьДочерний(Reference);
    
    Transforms = Reference.ДобавитьДочерний(ДокументDOM.СоздатьЭлемент(ПространствоИменЭП, "Transforms"));
    Transform = ДокументDOM.СоздатьЭлемент(ПространствоИменЭП, "Transform");
    УзелDOM = ДокументDOM.СоздатьАтрибут("Algorithm");
    УзелDOM.Значение = "http://www.w3.org/2001/10/xml-exc-c14n#";
    Transform.УстановитьУзелАтрибута(УзелDOM);
    Transforms.ДобавитьДочерний(Transform);
    
    // Алгоритм хэширования
    DigestMethod = ДокументDOM.СоздатьЭлемент(ПространствоИменЭП, "DigestMethod");
    УзелDOM = ДокументDOM.СоздатьАтрибут("Algorithm");
    УзелDOM.Значение = "http://www.w3.org/2001/04/xmldsig-more#gost3411";
    DigestMethod.УстановитьУзелАтрибута(УзелDOM);
    Reference.ДобавитьДочерний(DigestMethod);
    
    // Пустые узлы для заполнения криптопровайдером
    Reference.ДобавитьДочерний(ДокументDOM.СоздатьЭлемент(ПространствоИменЭП, "DigestValue"));
    Signature.ДобавитьДочерний(ДокументDOM.СоздатьЭлемент(ПространствоИменЭП, "SignatureValue"));
    
    // KeyInfo - информация о сертификате
    KeyInfo = Signature.ДобавитьДочерний(ДокументDOM.СоздатьЭлемент(ПространствоИменЭП, "KeyInfo"));
    SecurityTokenReference = ДокументDOM.СоздатьЭлемент(ПространствоИменСекрет, ПрефиксСекрет + ":SecurityTokenReference");
    KeyInfo.ДобавитьДочерний(SecurityTokenReference);
    
    RefToToken = ДокументDOM.СоздатьЭлемент(ПространствоИменСекрет, ПрефиксСекрет + ":Reference");
    УзелDOM = ДокументDOM.СоздатьАтрибут("ValueType");
    УзелDOM.Значение = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3";
    RefToToken.УстановитьУзелАтрибута(УзелDOM);
    
    УзелDOM = ДокументDOM.СоздатьАтрибут("URI");
    УзелDOM.Значение = "#" + ИД;
    RefToToken.УстановитьУзелАтрибута(УзелDOM);
    SecurityTokenReference.ДобавитьДочерний(RefToToken);
    
    // Помечаем Body идентификатором подписи
    Body = Корень.ПолучитьЭлементыПоИмени(ПрефиксСОАП, "Body")[0];
    УзелDOM = ДокументDOM.СоздатьАтрибут(ПространствоИменУтилити, ПрефиксУтилити + ":Id");
    УзелDOM.Значение = ДанныеИД;
    Body.УстановитьУзелАтрибута(УзелDOM);
    
    // Запись результата в строку
    ЗаписьXML = Новый ЗаписьXML;
    ЗаписьXML.УстановитьСтроку(Новый ПараметрыЗаписиXML("UTF-8"));
    ЗаписьXML.ЗаписатьОбъявлениеXML;
    ЗаписьDOM = Новый ЗаписьDOM;
    ЗаписьDOM.Записать(Корень, ЗаписьXML);
    
    Возврат ЗаписьXML.Закрыть();
КонецФункции

Важно: этот код подготавливает XML-структуру и оставляет теги <DigestValue> и <SignatureValue> пустыми. Чтобы подписать получившийся файл реальной ЭЦП (например, ГОСТ или RSA), необходимо использовать криптопровайдер. В операционной среде Windows это реализуется вызовом внешней COM-компоненты CAdESCOM.SignedXML:


// Пример подписания XML через компоненту КриптоПро под Windows
SignedXML = Новый COMОбъект("CAdESCOM.SignedXML");
SignedXML.Content = ПодготовленнаяСтрокаXML;
// Настраиваем тип подписи по шаблону (MustUnderstand = Истина)
SignedXML.SignatureType = 2; // CADESCOM_XML_SIGNATURE_TYPE_TEMPLATE
// Передаем отпечаток сертификата для подписи
SignedXML.Sign(ОтпечатокСертификата);
РезультатПодписиXML = SignedXML.Export();

Если сервер 1С запущен под Linux, COM-объекты недоступны. В этом случае подписание осуществляется через вызов утилиты командной строки КриптоПро (команда ЗапуститьПриложение) или с помощью создания отдельного микросервиса на Python/C#, который будет выступать криптографическим шлюзом.

Шаг 4. Импорт SSL-сертификатов (cacert.pem) в платформу 1С

При использовании HTTPСоединение с протоколом HTTPS платформа 1С проверяет валидность цепочки сертификатов веб-сервиса. Если удаленный сервер использует корпоративный или самоподписанный сертификат, 1С вернет ошибку проверки SSL.

Для решения этой проблемы необходимо зарегистрировать корневой сертификат веб-сервиса в хранилище доверенных сертификатов 1С. Выполним следующие действия:

  1. Экспортируем корневой и промежуточный сертификаты удаленного веб-сервиса через веб-браузер или утилиту OpenSSL в формате X.509 (.CER) в кодировке DER.
  2. Переходим в каталог установки платформы 1С на сервере (например, C:\Program Files\1cv8\Х.Х.Х.Х\bin\).
  3. Находим файл **cacert.pem** — это встроенное хранилище корневых сертификатов 1С.
  4. Преобразуем полученный сертификат в текстовый PEM-формат с помощью утилиты OpenSSL и дописываем его содержимое в конец файла **cacert.pem**.

Альтернативный, более безопасный вариант, не требующий изменения файлов платформы — динамическая инициализация защищенного соединения в коде с передачей пути к вашему локальному файлу сертификатов:


ПутьКФайлуСертификатов = "D:\Certificates\my_trusted_certs.pem";
СертификатыЦА = Новый СертификатыУдостоверяющихЦентровФайл(ПутьКФайлуСертификатов);
ЗащищенноеСоединение = Новый ЗащищенноеСоединениеOpenSSL(Неопределено, СертификатыЦА);

Резюме

Мы выяснили, что стандартная модель WSПрокси накладывает существенные ограничения при попытке работы по спецификации WS-Security. Для успешной интеграции с защищенными сервисами следует использовать HTTPСоединение в связке с ручной сборкой XML-запроса. Для простых сценариев достаточно передавать логин и пароль в секции заголовков UsernameToken. Для более жестких протоколов безопасности (таких как PasswordDigest или цифровая подпись XMLDSIG) необходимо производить расчет байтовых хешей в 1С или использовать внешние криптографические библиотеки.

← На главную