如何使用ClientInspector修改WCF消息头的MustUnderstand
我正在从WCF客户端调用非WCF服务。 WCF客户端包含设置为“1”的“MustUnderstand”标头属性。下面是一个典型的SOAP请求:如何使用ClientInspector修改WCF消息头的MustUnderstand
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<s:Header>
<o:Security s:mustUnderstand="1" xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<u:Timestamp u:Id="_0">
<u:Created>2010-08-23T20:48:52.680Z</u:Created>
<u:Expires>2010-08-23T20:53:52.680Z</u:Expires>
</u:Timestamp>
<o:UsernameToken u:Id="uuid-72ea0c0a-43aa-43b2-bed7-c2da13624105-1">
<o:Username>blablabla</o:Username>
<o:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">blablabla</o:Password>
</o:UsernameToken>
</o:Security>
</s:Header>
<s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<HeartbeatRequest xmlns="http://removed">
<DateTime xmlns="">8/23/2010 4:48:51 PM</DateTime>
<Message xmlns="">123</Message>
</HeartbeatRequest>
</s:Body>
现在,这里是我回来这个响应。
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Header>
<Misunderstood qname="o:Security" xmlns="http://www.w3.org/2002/06/soap-faults" xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" />
</soapenv:Header>
<soapenv:Body>
<soapenv:Fault xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<faultcode>soapenv:MustUnderstand</faultcode>
<faultstring>WSWS3173E: Error: Did not understand "MustUnderstand" header(s):{http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd}Security</faultstring>
</soapenv:Fault>
</soapenv:Body>
备注的mustUnderstand不被人理解的部分。
此服务的所有者已经表明它们允许具有WSSE命名空间前缀但实际上不在XSD中的元素,并执行一些其他处理来阻止它们接受MustUnderstand =“1”,因此我必须找到一种方法来发送MustUnderstand =“0”的消息。
我试图在使用MessageHeader属性的代理客户端的MessageContract中对此进行更改,但这并没有帮助。
接下来,我实现了一个自定义的客户端消息检查器。我创建了每MSDN类的自定义行为扩展元素和IEndpointBehavior,这些都是微不足道的,但这里的完整性:
public class ExClientBehavior : IEndpointBehavior
{
#region IEndpointBehavior Members
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
// no op
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
ExInspector inspector = new ExInspector();
clientRuntime.MessageInspectors.Add(inspector);
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
// no op
}
public void Validate(ServiceEndpoint endpoint)
{
// no op
}
#endregion
}
public class ExClientBehaviorExtensionElement : BehaviorExtensionElement
{
public override Type BehaviorType
{
get { return typeof(ExClientBehavior); }
}
protected override object CreateBehavior()
{
return new ExClientBehavior();
}
}
现在的实际检查:
public class ExInspector : IClientMessageInspector
{
#region IClientMessageInspector Members
public void AfterReceiveReply(ref Message reply, object correlationState)
{
// no op
return;
}
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
MessageBuffer buffer = request.CreateBufferedCopy(int.MaxValue);
Message newMessage = buffer.CreateMessage();
newMessage.Headers.RemoveAt(0);
newMessage.Headers.Add(MessageHeader.CreateHeader
(
request.Headers[0].Name,
request.Headers[0].Namespace,
string.Empty,
false,
string.Empty,
request.Headers[0].Relay
)
);
request = newMessage;
return null;
}
#endregion
}
正如你看到的,我创建通过缓冲副本创建一个新请求,然后删除安全头(只有一个头),并添加一个MustUnderstand设置为false的新头(为什么要这么做?MessageHeader.MustUnderstand是只读的)。我在这个方法中设置了一个断点,事实上,新的头文件被添加,newMessage被写回请求,并且newMessage.Headers [0] .MustUnderstand以及request.Headers [0] .MustUnderstand在此方法结束。
但是,发送到服务的消息仍然包含标头上的MustUnderstand =“1”!!!!!
下面是包括上述行为的app.config:
<configuration>
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="WebServiceSOAP" 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="TransportWithMessageCredential">
<transport clientCredentialType="Basic" proxyCredentialType="None" realm="" />
<message clientCredentialType="UserName" algorithmSuite="Default" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint
address="https://removed"
behaviorConfiguration="ovrExClientBehavior"
binding="basicHttpBinding"
bindingConfiguration="WebServiceSOAP"
contract="EWebService.EWebService"
name="WebServiceSOAP" />
</client>
<extensions>
<behaviorExtensions>
<add name="exClientBehavior" type="ExMessageInspector.ExClientBehaviorExtensionElement, ExMessageInspector, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
</behaviorExtensions>
</extensions>
<behaviors>
<endpointBehaviors>
<behavior name="ovrExClientBehavior">
<exClientBehavior />
</behavior>
</endpointBehaviors>
</behaviors>
</system.serviceModel>
所以我的问题:是否有可能在上方更改的mustUnderstand上发出的消息等,或以类似的方式?或者,在检查员更换安全标题后,它是否会在管道中强行恢复为真?
注意:服务所有者表示他们只知道另一个组织在.NET中使用此服务,并且该用户必须从头开始从本质上抛出WCF和WSE并创建SOAP消息并处理答复,可能使用POX POST或其他类似的。我们真的希望避免这种情况,因为我们需要在服务上调用一些操作。
此外,我们需要保持消息的主体和属性不变。
任何帮助将非常感谢!
我想知道为什么标准的互操作性存在,如果供应商不遵循它们。如果客户端检查器不工作,您可以尝试实现自定义消息编码器并修改其中的标头。
编辑:
这里的问题是,为什么你,如果你在同一时间宣布该服务不必了解与凭证头发送用户凭据进行身份验证=不必使用他们。你真的需要他们吗?
还有其他方法取决于您的要求。你需要时间戳吗?服务器上检查了时间戳吗?您是否有单一用户接听所有电话,或者您是否需要在不同电话之间区别用户?
如果您不需要时间戳或时间戳未选中,并且您只有一个用户名和密码,最简单的方法就是不要使用TranportWithMessageCredential安全模式。改用纯运输和地方标题说明客户端点配置里面,如:
<client>
<endpoint address="https://removed" binding="basicHttpBinding" bindingConfiguration="WebServiceSOPA" contract="EWebService.EWebService" name="WebServiceSOAP">
<headers>
<wsse:Security s:mustUnderstand="0" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<wsse:UsernameToken wsu:Id="SecurityToken-3f7f983f-66ce-480d-bce6-170632d33f92" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<wsse:Username>User</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">Pwd123</wsse:Password>
</wsse:UsernameToken>
</wsse:Security>
</headers>
</endpoint>
</client>
如果您有多个用户名或如果你需要用实际数据真实时间戳,您可以使用相同的方法,但不是静态的配置,你可以创建自定义代码中的头部并避免WCF安全性。这可以使用消息检查器完成。
不幸的是,这家供应商将MustUnderstand = 0与仍然强制标头中的(静态)用户名/密码结合起来。如果我们忽略它,我们找回一个缺少的演员(即缺少元素)的错误。我不认为时间戳是必需的。 嗯 - 我会尝试端点配置方法并回复评论。 – pelazem 2010-08-24 09:30:32
好的,谢谢拉迪斯拉夫!我尝试了你的方法,并且由于我们“足够幸运”拥有一个静态的用户名/密码,所以只用于安全性和静态头部,因为你只需要在运行时输入 - 运行起来!非常感谢你。 – pelazem 2010-08-24 14:17:36
拉迪斯拉夫,感谢您的方法,但我无法使用它。正如我在端点配置中添加了这个头文件,我得到了编译错误':前缀未定义'。你能否给出一个关于在这个头文件中应该使用的名字空间的评论。 – 2013-09-02 13:20:36