10

I have been all over the net for this. I've just been having a devil of a time doing it, and the vendor whose web service I'm trying to consume refuses to officially support WCF as a method of consumption.

I'm no web services expert, so I'll do my best to document and explain with this initial post, but by all means, request more information if you need it, and hopefully I'll be able to supply whatever is necessary.

The service

At my company, we use a vendor application that exposes a service. The application is written in java, and it looks like the wsdl was created with Apache Axis 1.2.

The code

My legacy code uses WSE 3.0. In particular, it uses the proxy classes that have "WSE" auto-tacked at the end. This allows me to use a much simpler authentication scheme (the only way I could get it to work). I don't need to use certificates. I use a derivative of SecurityPolicyAssertion, and wrap it in a Policy object that gets passed to the SetPolicy method of the client class. Here's all I need to do to create a working instance of the client:

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

My default, out-of-the-box code for WCF (generated with a service reference) does not accept credentials, so I know there's a problem right off the bat. I've read various things online about using different security or binding settings in my app.config, but nothing has ever completely worked. My most common error after copious tinkering is WSDoAllReceiver: Request does not contain required Security header.

Here's the app.config. Perhaps we could start by telling me what ought to change here to facilitate passing the credentials--again, I've seen varying opinions online.

<?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>

I have changed some of the attributes to obscure the specific service we are using (company policy and all that).

And here is the sample C# code so far (testing in a console app):

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

UPDATE

Read this SO post: wcf security . . ..

I have updated my app.config accordingly, and am now passing username and pwd in code. I am still receiving the same error:

WSDoAllReceiver: Request does not contain required Security header

20120517 UPDATE

A successful request (from 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>

Working on getting the WCF trace--will add shortly.

20120517 UPDATE 2

And here's the envelope from 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 I have tried implementing the solution in the post that Mike Miller links to in the comments. Now I receive the following error (no message ends up getting sent because something's barfing on the scheme):

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

And in case anyone wants to ask, yes, I need to send over http, and yes, I'm aware that credentials are sent as unencrypted strings :-)

4

1 に答える 1

11

必要なのは、wcfootbでサポートされていないhttpトランスポートを介してユーザー名トークンを送信することです。さらに、トークンはnonce/createdを使用します。これもootbではありません。2つのオプションがあります:

  1. このossプロジェクトは、nonce/createdをユーザー名トークンに追加します。このossプロジェクトは、httpを介してユーザー名を送信する機能を追加します。両方のプロジェクトを組み合わせる必要があります。

  2. ws-securityは通常複雑であると見なされますが、最も単純な形式(ユーザー名)で使用します。最も簡単なのは、wcfセキュリティ設定をすべてまとめて閉じ、メッセージインスペクターで自分でセキュリティヘッダー全体を作成することです。ご覧のとおり、ほとんどのヘッダーは静的なxmlノードであり、ほとんどの値は非常に明確です(ユーザー名を知っています)。トリッキーな2つは、このossプロジェクトで実行する方法を確認できるナンスとタイムスタンプ(各1行)だけです。このオプションには、より簡単なバリエーションがあります。結局、 CUBを使用し、timestmpa/nonceをプッシュするカスタムエンコーダーを実装します。私は後者を選びますが、CUBを開発してから偏見があります...

カスタムエンコーディングの「messageVersion」プロパティで設定できるws-addressingヘッダーもあります。wsaプレフィックス定義でエンベロープヘッダーを省略したため、正確な値がわかりません。

個人的に助けが必要な場合は(セキュリティ上の制限があるようですので)、ぜひ私のブログからメールを送ってください。

編集:私はあなたのためにそれを実装しました。次の手順を実行します:

  1. cubをダウンロードして、それをよく理解してください(内部ではなく、ブログ投稿によると、それを使用する方法だけです)

  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. app.configのプロジェクトTestClientでは、バインディング要素にmessageVersionプロパティがあります。エンベロープのルートを公開していないため、はっきりとはわかりませんが、おそらくSoap11WSAddressingAugust2004またはSoap11WSAddressing10(または代わりにSoap12を使用したこれらのいずれか)に設定する必要があります。

幸運を!

于 2012-05-19T00:58:50.067 に答える