(Попытка) перейти от WSE 3.0 к WCF для кода клиента

Я был в сети для этого. У меня просто было дьявола времени, и поставщик, чей веб-сервис, который я пытаюсь использовать, отказывается официально поддерживать WCF как метод потребления.

Я не эксперт в области веб-сервисов, поэтому я сделаю все возможное, чтобы документировать и объяснять с помощью этого начального сообщения, но, во всяком случае, запрашивать дополнительную информацию, если вам это нужно, и, надеюсь, я смогу предоставить все необходимо.

Сервис

В моей компании мы используем приложение поставщика, которое предоставляет услугу. Приложение написано в java, и похоже, что wsdl был создан с Apache Axis 1.2.

Код

В моем устаревшем коде используется WSE 3.0. В частности, он использует прокси-классы, у которых в конце есть автоматическая привязка "WSE". Это позволяет мне использовать гораздо более простую схему аутентификации (единственный способ заставить ее работать). Мне не нужно использовать сертификаты. Я использую производную от SecurityPolicyAssertion и переношу ее в объект Policy, который передается методу SetPolicy класса клиента. Здесь все, что мне нужно сделать, чтобы создать рабочий экземпляр клиента:

MyWebServiceWse api = new MyWebServiceWse();
api.Url = myUrl;
api.SetPolicy(new Policy(new MyDerivedSecurityAssertion(user, pass)));

Мой стандартный, готовый код для WCF (сгенерированный с помощью ссылки на службу) не принимает учетные данные, поэтому я знаю, что есть проблема с самого начала. Я читал различные вещи в Интернете об использовании разных security или настроек привязки в моем app.config, но ничто никогда не работало полностью. Моя самая распространенная ошибка после обильного возиться WSDoAllReceiver: Request does not contain required Security header.

Здесь app.config. Возможно, мы могли бы начать с того, что расскажем мне, что должно измениться здесь, чтобы облегчить передачу полномочий - опять же, я видел разные мнения в Интернете.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <system.serviceModel>
        <bindings>
            <basicHttpBinding>
                <binding name="MySoapBinding" closeTimeout="00:01:00"
                    openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
                    allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
                    maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
                    messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered"
                    useDefaultWebProxy="true">
                    <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
                        maxBytesPerRead="4096" maxNameTableCharCount="16384" />
                    <security mode="None">
                        <transport clientCredentialType="None" proxyCredentialType="None"
                            realm="" />
                        <message clientCredentialType="UserName" algorithmSuite="Default" />
                    </security>
                </binding>
            </basicHttpBinding>
        </bindings>
        <client>
            <endpoint address="http://xyz:12345/services/MyService"
                binding="basicHttpBinding" bindingConfiguration="MySoapBinding"
                contract="MyNS.MyService" name="MyService" />
        </client>
    </system.serviceModel>
</configuration>

Я изменил некоторые атрибуты, чтобы скрыть конкретную службу, которую мы используем (политика компании и все такое).

И вот пример кода С# до сих пор (тестирование в консольном приложении):

MyClient client = new MyClient();
client.listMethod();

UPDATE

Прочтите эту публикацию SO: wcf security.,..

Я обновил свой app.config соответственно, и теперь передаю имя пользователя и pwd в коде. Я все еще получаю ту же ошибку:

WSDoAllReceiver: Request does not contain required Security header

20120517 UPDATE

Успешный запрос (от WSE3):

  <soap:Header>
    <wsa:Action>
    </wsa:Action>
    <wsa:MessageID>urn:uuid:cb739422-c077-4eec-8cb2-686837b76878</wsa:MessageID>
    <wsa:ReplyTo>
      <wsa:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</wsa:Address>
    </wsa:ReplyTo>
    <wsa:To>http://removed-for-security</wsa:To>
    <wsse:Security soap:mustUnderstand="1">
      <wsu:Timestamp wsu:Id="Timestamp-e13feaf9-33d9-47bf-ab5b-60b4611eb81a">
        <wsu:Created>2012-05-17T11:25:41Z</wsu:Created>
        <wsu:Expires>2012-05-17T11:30:41Z</wsu:Expires>
      </wsu:Timestamp>
      <wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="SecurityToken-00c26e1a-3b3b-400f-a99a-3aa54cf8c8ff">
        <wsse:Username>change-to-protect-the-innocent</wsse:Username>
        <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">nice-try</wsse:Password>
        <wsse:Nonce>KJMvUuWF2eO2uIJCuxJC4A==</wsse:Nonce>
        <wsu:Created>2012-05-17T11:25:41Z</wsu:Created>
      </wsse:UsernameToken>
    </wsse:Security>
  </soap:Header>
  <soap:Body>
    <listChannels xmlns="http://removed-for-security">
      <rowfrom>0</rowfrom>
      <rowto>10</rowto>
    </listChannels>
  </soap:Body>
</soap:Envelope>

Работа над получением трассировки WCF - будет добавлена ​​в ближайшее время.

20120517 ОБНОВЛЕНИЕ 2

А вот конверт из WCF:

  <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
    <s:Header>
      <Action s:mustUnderstand="1" xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none"></Action>
    </s:Header>
    <s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
      <listChannels xmlns="http://removed-for-security">
        <rowfrom>1</rowfrom>
        <rowto>2147483647</rowto>
      </listChannels>
    </s:Body>
  </s:Envelope>

20120518 UPDATE Я попытался реализовать решение в сообщении, которое Mike Miller ссылается на комментарии. Теперь я получаю следующую ошибку (никакое сообщение не попадает в сообщение, потому что что-то работает на схеме):

The provided URI scheme 'http' is invalid; expected 'https'.

И в случае, если кто-то хочет спросить, да, мне нужно отправить через http, и да, я знаю, что учетные данные отправляются как незашифрованные строки: -)

Ответ 1

Вам нужно отправить токен имени пользователя через http-транспорт, который не поддерживается в wcf ootb. Кроме того, ваш токен использует nonce/созданный, который также не является ootb. у вас есть 2 варианта:

  1. этот проект oss добавляет nonce/созданный к токену имени пользователя. этот проект oss добавляет возможность отправлять имя пользователя через http. вам нужно будет объединить оба проекта вместе.

  2. ws-security обычно считается сложным, но вы используете его в простейшем виде (имя пользователя). проще всего было бы полностью удалить все настройки безопасности wcf и создать весь заголовок безопасности самостоятельно в инспекторе сообщений ! Как видите, большинство заголовков - это просто статические узлы xml, и большинство значений довольно понятны (вы знаете имя пользователя). единственные хитрые два - это nonce и временные метки, которые вы можете посмотреть, как это сделать в этом проекте oss (по одной строке каждый). Существует вариант этой опции, который может быть проще - в конце концов используйте CUB и реализуйте собственный кодировщик, который выдвигает timestmpa/nonce. Я бы пошел на последнее, но я предвзят, так как я разработал CUB...

Также есть заголовки ws-адресации, которые вы можете настроить в свойстве кодировки "messageVersion". Я не могу сказать точное значение, так как вы пропустили заголовок конверта с определением префикса wsa.

Если вам нужна помощь в частном порядке (поскольку у вас, похоже, есть ограничения безопасности), обязательно отправьте мне электронное письмо из моего блога.

РЕДАКТИРОВАТЬ: я реализовал это для вас. Следуй этим шагам:

  1. скачайте детёныша и ознакомьтесь с ним (не внутренним, а просто как его использовать согласно сообщению в блоге)

  2. добавить ссылку на System.Runtime.Serialization.dll в проект ClearUsernameBinding

  3. добавьте новый файл в этот проект: UsernameExEncoder.cs. Вставьте этот контент:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.ServiceModel.Channels;
    using System.IO;
    using System.Xml;
    using System.Security.Cryptography;
    
    namespace Webservices20.BindingExtensions
       {
        class UsernameExEncoderBindingElement : MessageEncodingBindingElement
        {
        MessageEncodingBindingElement inner;        
    
        public UsernameExEncoderBindingElement(MessageEncodingBindingElement inner)
        {
            this.inner = inner;            
        }
    
        public override IChannelFactory<TChannel> BuildChannelFactory<TChannel>(BindingContext context)
        {
            context.BindingParameters.Add(this);
            var res = base.BuildChannelFactory<TChannel>(context);
            return res;
        }
    
        public override bool CanBuildChannelFactory<TChannel>(BindingContext context)
        {
            var res = base.CanBuildChannelFactory<TChannel>(context);
            return res;
        }
    
        public override MessageEncoderFactory CreateMessageEncoderFactory()
        {
            return new UsernameExEncoderFactory(this.inner.CreateMessageEncoderFactory());
        }      
    
        public override MessageVersion MessageVersion
        {
            get
            {
                return this.inner.MessageVersion;
            }
            set
            {
                this.inner.MessageVersion = value;
            }
        }
    
        public override BindingElement Clone()
        {
            var c = (MessageEncodingBindingElement)this.inner.Clone();
            var res = new UsernameExEncoderBindingElement(c);
            return res;
        }
    
        public override T GetProperty<T>(BindingContext context)
        {
            var res = this.inner.GetProperty<T>(context);
            return res;
        }
    }
    
    class UsernameExEncoderFactory : MessageEncoderFactory
    {
        MessageEncoderFactory inner;        
    
        public UsernameExEncoderFactory(MessageEncoderFactory inner)
        {
            this.inner = inner;            
        }
    
        public override MessageEncoder Encoder
        {
            get { return new UsernameExEncoder(inner.Encoder); }
        }
    
        public override MessageVersion MessageVersion
        {
            get { return this.inner.MessageVersion; }
        }
    
    }
    
    class UsernameExEncoder : MessageEncoder
    {
        MessageEncoder inner;
    
        public override T GetProperty<T>()
        {
            return inner.GetProperty<T>();
        }
    
        public UsernameExEncoder(MessageEncoder inner)
        {
            this.inner = inner;
        }
    
        public override string ContentType
        {
            get { return this.inner.ContentType; }
        }
    
        public override string MediaType
        {
            get { return this.inner.MediaType; }
        }
    
        public override MessageVersion MessageVersion
        {
            get { return this.inner.MessageVersion; }
        }
    
        public override bool IsContentTypeSupported(string contentType)
        {
            return this.inner.IsContentTypeSupported(contentType);
        } 
    
        public override Message ReadMessage(ArraySegment<byte> buffer, BufferManager bufferManager, string contentType)
        {
            return this.inner.ReadMessage(buffer, bufferManager, contentType);
        }
    
        public override Message ReadMessage(System.IO.Stream stream, int maxSizeOfHeaders, string contentType)
        {
            return this.inner.ReadMessage(stream, maxSizeOfHeaders, contentType);
        }
    
        public override ArraySegment<byte> WriteMessage(Message message, int maxMessageSize, BufferManager bufferManager, int messageOffset)
        {   
            //load the message to dom
            var mem = new MemoryStream();
            var x = XmlWriter.Create(mem);
            message.WriteMessage(x);
            x.Flush();
            mem.Flush();
            mem.Position = 0;
            XmlDocument doc = new XmlDocument();
            doc.Load(mem);
    
            //add the missing elements
            var token = doc.SelectSingleNode("//*[local-name(.)='UsernameToken']");
            var created = doc.CreateElement("Created", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
            var nonce = doc.CreateElement("Nonce", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
            token.AppendChild(created);
            token.AppendChild(nonce);
    
            //set nonce value
            byte[] nonce_bytes = new byte[16];
            RandomNumberGenerator rndGenerator = new RNGCryptoServiceProvider();
            rndGenerator.GetBytes(nonce_bytes);
            nonce.InnerText = Convert.ToBase64String(nonce_bytes);
    
            //set create value
            created.InnerText = XmlConvert.ToString(DateTime.Now.ToUniversalTime(), "yyyy-MM-ddTHH:mm:ssZ");
    
            //create a new message
            var r = XmlReader.Create(new StringReader(doc.OuterXml));
            var newMsg = Message.CreateMessage(message.Version, message.Headers.Action, r);
    
            return this.inner.WriteMessage(newMsg, maxMessageSize, bufferManager, messageOffset);
        }
    
    
    
    
        public override void WriteMessage(Message message, System.IO.Stream stream)
        {
            this.inner.WriteMessage(message, stream);
        }
    }
    }
    
  4. В файле ClearUsernameBinding.cs замените это:

    res.Add(new TextMessageEncodingBindingElement() { MessageVersion = this.messageVersion});

    с этим:

    var textEncoder = new TextMessageEncodingBindingElement() { MessageVersion = this.messageVersion }; res.Add(new UsernameExEncoderBindingElement(textEncoder));

  5. В проекте TestClient в app.config есть свойство messageVersion для элемента привязки. Вы не опубликовали корень своего конверта, поэтому я не могу знать наверняка, но, вероятно, вам нужно установить для него Soap11WSAddressingAugust2004 или Soap11WSAddressing10 (или один из них вместо Soap12).

Удачи!