I have a WCF client connecting to a Java based Axis2 web service (outside my control). It is about to have WS-Security applied to it, and I need to fix the .NET client. However, I am struggling to provide the correct authentication. I am aware that WSE 3.0 might make it easier, but I would prefer not to revert to an obsolete technology.
我有一个WCF客户端连接到一个基于Java的Axis2 web服务(在我的控制之外)。它即将应用WS-Security,我需要修复。net客户机。但是,我正在努力提供正确的身份验证。我知道WSE 3.0可能会让它变得更容易,但我不希望回到过时的技术。
Similar issues (unsolved), include this, this and this.
类似的问题(未解决),包括这个,这个和这个。
The SOAP message should look like this:
SOAP消息应该如下所示:
<wsse:UsernameToken>
<wsse:Username><!-- Removed--></wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest"><!-- Removed--></wsse:Password>
<wsse:Nonce><!-- Removed--></wsse:Nonce>
<wssu:Created>2010-05-28T12:50:33.675+01:00</wssu:Created>
</wsse:UsernameToken>
However, mine looks like this:
然而,我的看起来是这样的:
<s:Header>
<h:Security xmlns:h="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"></h:Security>
<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-06-23T10:31:23.441Z</u:Created>
<u:Expires>2010-06-23T10:36:23.441Z</u:Expires>
</u:Timestamp>
<o:UsernameToken u:Id="uuid-d329b3b2-6a1f-4882-aea6-ec6b8a492de7-1">
<o:Username>
<!-- Removed-->
</o:Username>
<o:Password>
<!-- Removed-->
</o:Password>
</o:UsernameToken>
</o:Security>
</s:Header>
My client looks like this: P.S. Note the required SecurityHeaderType param. What is that?
我的客户看起来是这样的:P.S.请注意所需的SecurityHeaderType参数。那是什么?
public MyAck SendRequest(MyRequest request)
{
RemoteServicePortTypeClient client = new RemoteServicePortTypeClient();
client.ClientCredentials.UserName.UserName = "JAY";
client.ClientCredentials.UserName.Password = "AND";
// what is the difference between the two different Credential types??
//client.ClientCredentials.HttpDigest.ClientCredential.UserName = "SILENT";
//client.ClientCredentials.HttpDigest.ClientCredential.Password = "BOB";
SecurityHeaderType sht = new SecurityHeaderType();
//sht.Any = ???; // How do I use this???
//sht.AnyAttr = ???; // How do I use this ???
// SecurityHeaderType is a required parameter
return client.RemoteServiceOperation_Provider(sht, request);
}
Current binding is as follows:
当前绑定如下:
<basicHttpBinding>
<binding name="CustomBinding">
<security mode="TransportWithMessageCredential">
<transport clientCredentialType="None"></transport>
<message clientCredentialType="UserName" />
</security>
</binding>
</basicHttpBinding>
I've also tried a custom binding and got a similar error:
我还尝试了一个自定义绑定,得到了类似的错误:
<customBinding>
<binding name="myCustomBindingConfig">
<security authenticationMode="UserNameOverTransport"
messageSecurityVersion="WSSecurity11WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11"
securityHeaderLayout="Strict"
includeTimestamp="false"></security>
<textMessageEncoding messageVersion="Soap11"></textMessageEncoding>
<httpsTransport />
</binding>
</customBinding>
And endpoint (Address obviously changed...):
和端点(地址明显改变…):
<endpoint address="https://www.somecompany.com/uat/axis/services/RemoteServiceOperation_Provider"
binding="basicHttpBinding" bindingConfiguration="CustomBinding"
contract="RemoteService.RemoteServicePortType"
name="RemoteService_UAT" />
The custom fault that is being returned is as follows:
所返回的自定义错误如下:
<ErrorID>0</ErrorID>
<ErrorType>UNEXPECTED</ErrorType>
<ErrorDescription><![CDATA[Array index out of range: 0]]></ErrorDescription>
<TimeStamp>2010-06-23T13:28:54Z</TimeStamp>
I've read lots about custom headers, tokens, bindings and my brain is completely confused. Can anyone suggest a step by step process for sending the message in the right format?
我读过很多关于自定义标题、令牌、绑定和我的大脑完全混乱的东西。有谁能建议一步一步地以正确的格式发送消息?
This appears to be the way forward for WCF, using custom tokens, but how should one apply the digest and nonce as required?
这似乎是WCF的发展方向,使用自定义令牌,但是如何根据需要应用摘要和nonce呢?
Any help welcomed.
任何帮助表示欢迎。
UPDATE
I've had some limited success. I've used the Microsoft.Web.Services3 library to create a UsernameToken with the correct digest. I've then created my own custom behavior and in the BeforeSendRequest method I've done the following to inject the header:
我的成功是有限的。我使用了Microsoft.Web。Services3库用于创建带有正确摘要的UsernameToken。然后我创建了我自己的自定义行为,在前面所介绍的方法中,我已经做了以下工作来注入标题:
object IClientMessageInspector.BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
{
UsernameToken ut = new UsernameToken("USERNAME", "PASSWORD", PasswordOption.SendHashed);
XmlElement securityElement = ut.GetXml(new XmlDocument());
MessageHeader myHeader = MessageHeader.CreateHeader("Security", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", securityElement, false);
request.Headers.Add(myHeader);
return Convert.DBNull;
}
I add the behavior like so:
我加上如下行为:
CustomBehavior behavior = new CustomBehavior("USERNAME", "PASSWORD");
client.Endpoint.Behaviors.Add(behavior);
I can now see the headers going across:
我现在可以看到页眉穿过:
<s:Header>
<Security xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<wsse:UsernameToken wsu:Id="SecurityToken-c6aeb72d-4d36-4650-abd3-33cc66caac6d" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<wsse:Username>
<!-- Removed-->
</wsse:Username>
<wsse:Password>
<!-- Removed-->
</wsse:Password>
<wsse:Nonce>
<!-- Removed-->
</wsse:Nonce>
<wsu:Created>2010-06-24T16:23:58Z</wsu:Created>
</wsse:UsernameToken>
</Security>
</s:Header>
But I'm getting the error:
但我得到了一个错误:
<soapenv:Fault>
<faultcode xmlns="">soapenv:Server</faultcode>
<faultstring xmlns="">WSDoAllReceiver: security processing failed; nested exception is:
org.apache.ws.security.WSSecurityException: General security error (WSSecurityEngine: Callback supplied no password for: USERNAME)</faultstring>
<faultactor xmlns="">urn:Remote_Provider</faultactor>
<detail xmlns="">
<CUSTOMError xmlns="urn:customerror:v01">
<ErrorID>0</ErrorID>
<ErrorType>UNEXPECTED</ErrorType>
<ErrorDescription><![CDATA[WSDoAllReceiver: security processing failed; nested exception is:
org.apache.ws.security.WSSecurityException: General security error (WSSecurityEngine: Callback supplied no password for: USERNAME)]]></ErrorDescription>
<TimeStamp>2010-06-24T17:23:59Z</TimeStamp>
</CUSTOMError>
</detail>
</soapenv:Fault>
There appears to be a missing Type attribute on the password node:
密码节点上似乎有一个缺失的类型属性:
Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest"
However, I'm not sure if the security tracing and logging settings are blanket removing the attributes and content of those nodes. I've attempted to use the logKnownPii setting in the diagnostics logging, but the security information remains obscured. Any ideas on that one?
但是,我不确定安全跟踪和日志记录设置是否覆盖了删除这些节点的属性和内容。我尝试在诊断日志记录中使用logKnownPii设置,但是安全性信息仍然很模糊。有什么想法吗?
3 个解决方案
#1
5
I can confirm that the UPDATE from my question actually works:
我可以确认我的问题的更新确实有效:
object IClientMessageInspector.BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
{
UsernameToken ut = new UsernameToken("USERNAME", "PASSWORD", PasswordOption.SendHashed);
XmlElement securityElement = ut.GetXml(new XmlDocument());
MessageHeader myHeader = MessageHeader.CreateHeader("Security", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", securityElement, false);
request.Headers.Add(myHeader);
return Convert.DBNull;
}
And the client:
和客户端:
CustomBehavior behavior = new CustomBehavior("USERNAME", "PASSWORD");
client.Endpoint.Behaviors.Add(behavior);
The error message was unrelated. The security header works with a very simple basicHttpBinding:
错误消息是不相关的。安全头使用一个非常简单的basicHttpBinding:
<basicHttpBinding>
<binding name="BasicSOAPBinding">
<security mode="Transport" />
</binding>
</basicHttpBinding>
Code sample of this in action can be found on my blog: http://benpowell.org/supporting-the-ws-i-basic-profile-password-digest-in-a-wcf-client-proxy/
这方面的代码示例可以在我的博客上找到:http://benpowell.org/supporting- ws-i-basic-profile- proxy/
#2
2
This question is well written -- many thanks. In reference to @Junto's "How do I use this" comment, it turns out that the SecurityHeader param on the service method can be used to add the header. I've included an example below. I believe that what's happening is that the SvcUtil.exe tool is barfing when trying to read the WS* DTDs. This is not obvious when you use the "Add Service Reference" wizard. But it is very obvious when you run svcutil.exe from the command line. Because svcutil.exe fails to read the WS* DTD's, the SecurityHeader object is not well developed. But Microsoft gives you an out with the .Any property. You can serialize the UsernameToken class right into the .Any property and your header will be added to the message. Again, thanks for this excellent question.
这个问题写得很好——非常感谢。对于@Junto的“如何使用这个”注释,可以使用服务方法上的SecurityHeader param来添加头。下面是一个例子。我相信正在发生的是SvcUtil。exe工具在尝试读取WS* DTDs时是barfing。当您使用“添加服务引用”向导时,这并不明显。但是当你运行svcutil时,它是非常明显的。来自命令行的exe。因为svcutil。exe无法读取WS* DTD, SecurityHeader对象没有得到很好的开发。但是微软给你一个。any属性。您可以将UsernameToken类序列化到. any属性中,并将标题添加到消息中。再次感谢你提出的这个问题。
How to use the SecurityHeader parameter to add a UsernameToken security header:
如何使用SecurityHeader参数添加UsernameToken安全header:
Required tools:
所需工具:
Fiddler2 (or similar) -- you really can't figure any of this out without inspecting the http headers.
Fiddler2(或类似的)——如果不检查http头,您真的无法解决任何问题。
Required Reference:
需要参考:
Microsoft.Web.Services3.dll -- you can reference this 2.0 framework assembly from your 4.0 assembly
WCF service call:
WCF服务电话:
// Initialization of the service...
_service = new MyService("MyEndpoint", RemoteUri);
// etc.
// Calling the service -- note call to GetSecurityHeader()
_service.ServiceAction(GetSecurityHeader(), "myParam1");
// etc.
/// <summary>
/// Construct the WSE 3.0 Security Header
/// </summary>
private SecurityHeader GetSecurityHeader()
{
SecurityHeader h = new SecurityHeader();
UsernameToken t = new UsernameToken(RemoteLogin, RemotePassword, PasswordOption.SendPlainText);
h.Any = new XmlElement[1];
h.Any[0] = t.GetXml(new XmlDocument());
return h;
}
App.config:
App.config:
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="MyBinding" closeTimeout="00:01:00" openTimeout="00:01:00"
receiveTimeout="00:10:00" sendTimeout="00:10:00" allowCookies="false"
bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
maxBufferSize="1048576" maxBufferPoolSize="524288" maxReceivedMessageSize="1048576"
messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered"
useDefaultWebProxy="true">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
maxBytesPerRead="4096" maxNameTableCharCount="16384" />
<security mode="Transport">
<transport clientCredentialType="None" proxyCredentialType="None"
realm="" />
<message clientCredentialType="UserName" algorithmSuite="Default" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="http://myservice.com/service.asmx"
binding="basicHttpBinding" bindingConfiguration="MyBinding" contract="MyContract"
name="MyEndpoint" />
</client>
</system.serviceModel>
#3
1
I had a similar problem recently and gave up searching for a non-WSE solution. After a couple days of pulling my hair out I ended downloading the WSE 3.0 SDK, generating a proxy class using WseWsdl3.exe, and creating a new policy for the UsernameToken. I was up and running in 15min. The following is currently working for me.
最近我遇到了类似的问题,于是放弃了寻找非wse解决方案。经过几天的努力,我最终下载了WSE 3.0 SDK,使用WseWsdl3生成了一个代理类。并为UsernameToken创建一个新策略。我在15分钟内就开始跑步了。下面是我的工作。
RemoteService service = new RemoteService(); //generated class
UsernameToken token = new UsernameToken(username, password, PasswordOption.SendPlainText);
Policy policy = new Policy();
policy.Assertions.Add(new UsernameOverTransportAssertion());
service.SetClientCredential(token);
service.SetPolicy(policy);
var result = service.MethodCall();
#1
5
I can confirm that the UPDATE from my question actually works:
我可以确认我的问题的更新确实有效:
object IClientMessageInspector.BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
{
UsernameToken ut = new UsernameToken("USERNAME", "PASSWORD", PasswordOption.SendHashed);
XmlElement securityElement = ut.GetXml(new XmlDocument());
MessageHeader myHeader = MessageHeader.CreateHeader("Security", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", securityElement, false);
request.Headers.Add(myHeader);
return Convert.DBNull;
}
And the client:
和客户端:
CustomBehavior behavior = new CustomBehavior("USERNAME", "PASSWORD");
client.Endpoint.Behaviors.Add(behavior);
The error message was unrelated. The security header works with a very simple basicHttpBinding:
错误消息是不相关的。安全头使用一个非常简单的basicHttpBinding:
<basicHttpBinding>
<binding name="BasicSOAPBinding">
<security mode="Transport" />
</binding>
</basicHttpBinding>
Code sample of this in action can be found on my blog: http://benpowell.org/supporting-the-ws-i-basic-profile-password-digest-in-a-wcf-client-proxy/
这方面的代码示例可以在我的博客上找到:http://benpowell.org/supporting- ws-i-basic-profile- proxy/
#2
2
This question is well written -- many thanks. In reference to @Junto's "How do I use this" comment, it turns out that the SecurityHeader param on the service method can be used to add the header. I've included an example below. I believe that what's happening is that the SvcUtil.exe tool is barfing when trying to read the WS* DTDs. This is not obvious when you use the "Add Service Reference" wizard. But it is very obvious when you run svcutil.exe from the command line. Because svcutil.exe fails to read the WS* DTD's, the SecurityHeader object is not well developed. But Microsoft gives you an out with the .Any property. You can serialize the UsernameToken class right into the .Any property and your header will be added to the message. Again, thanks for this excellent question.
这个问题写得很好——非常感谢。对于@Junto的“如何使用这个”注释,可以使用服务方法上的SecurityHeader param来添加头。下面是一个例子。我相信正在发生的是SvcUtil。exe工具在尝试读取WS* DTDs时是barfing。当您使用“添加服务引用”向导时,这并不明显。但是当你运行svcutil时,它是非常明显的。来自命令行的exe。因为svcutil。exe无法读取WS* DTD, SecurityHeader对象没有得到很好的开发。但是微软给你一个。any属性。您可以将UsernameToken类序列化到. any属性中,并将标题添加到消息中。再次感谢你提出的这个问题。
How to use the SecurityHeader parameter to add a UsernameToken security header:
如何使用SecurityHeader参数添加UsernameToken安全header:
Required tools:
所需工具:
Fiddler2 (or similar) -- you really can't figure any of this out without inspecting the http headers.
Fiddler2(或类似的)——如果不检查http头,您真的无法解决任何问题。
Required Reference:
需要参考:
Microsoft.Web.Services3.dll -- you can reference this 2.0 framework assembly from your 4.0 assembly
WCF service call:
WCF服务电话:
// Initialization of the service...
_service = new MyService("MyEndpoint", RemoteUri);
// etc.
// Calling the service -- note call to GetSecurityHeader()
_service.ServiceAction(GetSecurityHeader(), "myParam1");
// etc.
/// <summary>
/// Construct the WSE 3.0 Security Header
/// </summary>
private SecurityHeader GetSecurityHeader()
{
SecurityHeader h = new SecurityHeader();
UsernameToken t = new UsernameToken(RemoteLogin, RemotePassword, PasswordOption.SendPlainText);
h.Any = new XmlElement[1];
h.Any[0] = t.GetXml(new XmlDocument());
return h;
}
App.config:
App.config:
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="MyBinding" closeTimeout="00:01:00" openTimeout="00:01:00"
receiveTimeout="00:10:00" sendTimeout="00:10:00" allowCookies="false"
bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
maxBufferSize="1048576" maxBufferPoolSize="524288" maxReceivedMessageSize="1048576"
messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered"
useDefaultWebProxy="true">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
maxBytesPerRead="4096" maxNameTableCharCount="16384" />
<security mode="Transport">
<transport clientCredentialType="None" proxyCredentialType="None"
realm="" />
<message clientCredentialType="UserName" algorithmSuite="Default" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="http://myservice.com/service.asmx"
binding="basicHttpBinding" bindingConfiguration="MyBinding" contract="MyContract"
name="MyEndpoint" />
</client>
</system.serviceModel>
#3
1
I had a similar problem recently and gave up searching for a non-WSE solution. After a couple days of pulling my hair out I ended downloading the WSE 3.0 SDK, generating a proxy class using WseWsdl3.exe, and creating a new policy for the UsernameToken. I was up and running in 15min. The following is currently working for me.
最近我遇到了类似的问题,于是放弃了寻找非wse解决方案。经过几天的努力,我最终下载了WSE 3.0 SDK,使用WseWsdl3生成了一个代理类。并为UsernameToken创建一个新策略。我在15分钟内就开始跑步了。下面是我的工作。
RemoteService service = new RemoteService(); //generated class
UsernameToken token = new UsernameToken(username, password, PasswordOption.SendPlainText);
Policy policy = new Policy();
policy.Assertions.Add(new UsernameOverTransportAssertion());
service.SetClientCredential(token);
service.SetPolicy(policy);
var result = service.MethodCall();