尝试从WSE 3.0迁移到WCF以用于客户端代码

10

我在网络上到处寻找答案,但一直很难做到。提供网络服务的供应商拒绝官方支持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默认代码(使用服务引用生成)不接受凭据,所以我知道问题一开始就出现了。我在网上看到了关于在我的app.config中使用不同的security或绑定设置的各种内容,但没有任何一种方法完全有效。我最常见的错误是大量修改后出现的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>

我已经更改了某些属性,以遮盖我们正在使用的具体服务(公司政策等)。

以下是目前的示例C#代码(在控制台应用程序中进行测试):

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

更新

请阅读此 StackOverflow 帖子:wcf 安全性 . . .

我已经相应地更新了我的 app.config,并在代码中传递了用户名和密码。然而,我仍然收到同样的错误:

WSDoAllReceiver: Request does not contain required Security header

20120517 更新

来自 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 更新 我已经尝试实施Mike Miller在评论中链接的帖子中的解决方案。现在我收到以下错误(由于某些东西出错了,没有消息最终被发送):

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

如果有人想问,是的,我需要通过http发送,是的,我知道凭据会作为未加密的字符串发送 :-)


1
这是同一个问题吗?https://dev59.com/g-o6XIcBkEYKwwoYTy8d - Mike Miller
1
我认为最后一次更新指向了这个方向。使用WCF,不要通过HTTP发送消息级客户端凭据。http://stackoverflow.com/questions/9191104/wcf-service-username-password-authentication-over-http-without-ssl - Yiğit Yener
只是一个提示,我已经编辑了我的答案,现在它包含了实际的实现。 - Yaron Naveh
谢谢,我明天回到工作岗位后会尝试实现。 - Brian Warshaw
你还记得第一次更新中链接中的 mode="TransportCredentialOnly" 吗?你的安全节点中有它吗? - Grzegorz W
显示剩余6条评论
1个回答

11

您需要通过HTTP传输发送用户名令牌,但这在WCF中不受支持。此外,您的令牌使用了nonce/created,这也不是默认提供的。您有两个选择:

  1. 这个oss项目在用户名令牌中添加了nonce/created。这个oss项目增加了通过http发送用户名的能力。你需要将两个项目合并在一起。

  2. ws-security通常被认为很复杂,但你可以以最简单的方式(用户名)使用它。最简单的方法是完全忽略任何wcf安全设置,并在message inspector中自己创建整个安全头!正如你所看到的,大多数标头只是静态xml节点,大多数值都非常清晰(你知道用户名)。唯一棘手的是nonce和时间戳,你可以查看这个oss项目中如何做到这一点(每行一个)。还有一种更容易的选择 - 毕竟使用CUB并实现一个custom encoder来推动timestmpa/nonce。我会选择后者,但我有偏见,因为我开发了CUB...

还有ws-addressing头,您可以在自定义编码“messageVersion”属性上进行配置。由于您省略了带有wsa前缀定义的信封标头,因此我无法告诉确切的值。

如果您想私下获得帮助(因为您似乎有安全限制),请通过我的博客给我发送电子邮件。

编辑:我已经为您实现了它,请按照以下步骤操作:

  1. download cub and make yourself familiar with it (not the internals, just how to use it according to the blog post)

  2. add reference to System.Runtime.Serialization.dll to the project ClearUsernameBinding

  3. add a new file to that project: UsernameExEncoder.cs. Paste this content:

    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. In the file ClearUsernameBinding.cs replace this:

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

    with this:

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

  5. In the project TestClient in app.config there is a messageVersion property on the binding element. You have not published the root of your envelope so I cannot know for sure, but probably you need to set it to Soap11WSAddressingAugust2004 or Soap11WSAddressing10 (or one of these with Soap12 instead).

祝你好运!


Yaron - 我在工作网络上尝试获取CUB代码时遇到了代理违规问题(因为它位于Blogspot)。您是否有其他可用代码位置? - Brian Warshaw
代码在 Google Code 上 http://code.google.com/p/wcf-clear-username-binding/,博客只包含解释。 - Yaron Naveh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接