(正在尝试)从 WSE 3.0 迁移到客户端代码的 WCF
本文关键字:迁移 客户端 代码 WCF WSE | 更新日期: 2023-09-27 18:34:12
我为此到处都是。我刚刚在做这件事,而我尝试使用其 Web 服务的供应商拒绝正式支持 WCF 作为一种消费方法。
我不是 Web 服务专家,所以我会尽力在这篇初始帖子中记录和解释,但无论如何,如果您需要更多信息,请索取更多信息,希望我能够提供任何必要的信息。
服务内容
在我的公司,我们使用公开服务的供应商应用程序。该应用程序是用java编写的,看起来wsdl是用Apache Axis 1.2创建的。
代码
我的旧代码使用 WSE 3.0。特别是,它使用在末尾自动绑定了"WSE"的代理类。这允许我使用更简单的身份验证方案(这是我能让它工作的唯一方法(。我不需要使用证书。我使用 SecurityPolicyAssertion
的导数,并将其包装在传递给客户端类的 SetPolicy
方法的 Policy
对象中。以下是创建客户端工作实例所需做的所有操作:
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();
更新
阅读这篇SO帖子: WCF安全
. . .我已经相应地更新了我的 app.config,现在正在代码中传递用户名和 pwd。我仍然收到相同的错误:
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更新我尝试在迈克米勒在评论中链接到的帖子中实施解决方案。现在我收到以下错误(没有消息最终被发送,因为方案上有问题(:
The provided URI scheme 'http' is invalid; expected 'https'.
如果有人想问,是的,我需要通过 http 发送,是的,我知道凭据是作为未加密字符串发送的:-(
您需要通过 http 传输发送用户名令牌,这在 wcf ootb 中不受支持。 此外,您的令牌使用nonce/created,这也不是OOTB。 您有 2 个选项:
-
此 OSS 项目将 nonce/Created 添加到用户名令牌中。 此 OSS 项目增加了通过 HTTP 发送用户名的功能。您需要将两个项目组合在一起。
-
WS-Security 通常被认为是复杂的,但您可以以最简单的形式(用户名(使用它。 最简单的方法是同时关闭任何 WCF 安全设置,并在邮件检查器中自行创建整个安全标头!如您所见,大多数标头只是静态 xml 节点,并且大多数值都非常清晰(您知道用户名(。唯一棘手的两个是随机数和时间戳,您可以查看如何在此OSS项目中执行(每个一行(。此选项有一个变体可能更容易 - 毕竟使用 CUB 并实现一个推送 timestmpa/nonce 的自定义编码器。我会选择后者,但自从我开发 CUB 以来我有偏见......
还有 ws 寻址标头,您可以在自定义编码"messageVersion"属性上配置这些标头。我无法说出确切的值,因为您省略了带有 wsa 前缀定义的信封标题。
如果你私下需要帮助(因为你似乎有安全限制(,一定要从我的博客给我发一封电子邮件。
编辑:我已经为您实现了它。 请按照以下步骤操作:
-
下载 cub 并让自己熟悉它(不是内部结构,只是根据博客文章如何使用它(
-
添加对 System.Runtime.Serialization.dll 的引用到项目 ClearUsernameBinding
-
向该项目添加一个新文件:用户名ExEncoder.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); } } }
-
在文件 ClearUsernameBinding 中.cs替换以下内容:
res.Add(new TextMessageEncodingBindingElement() { MessageVersion = this.messageVersion});
有了这个:
var textEncoder = new TextMessageEncodingBindingElement() { MessageVersion = this.messageVersion }; res.Add(new UsernameExEncoderBindingElement(textEncoder));
-
在 app.config 的 TestClient 项目中,绑定元素上有一个 messageVersion 属性。您尚未发布信封的根目录,所以我无法确定,但可能需要将其设置为 Soap11WSAddressingAugust 2004 或 Soap11WSAddressing10(或其中一个与 Soap12 代替(。
祝你好运!