WCF 客户端使用 WS-Security UsernameToken PasswordDigest 身份验证方案使用 Axis 2 Web 服务时出错
Posted
技术标签:
【中文标题】WCF 客户端使用 WS-Security UsernameToken PasswordDigest 身份验证方案使用 Axis 2 Web 服务时出错【英文标题】:Error in WCF client consuming Axis 2 web service with WS-Security UsernameToken PasswordDigest authentication scheme 【发布时间】:2011-03-07 08:55:42 【问题描述】:我有一个 WCF 客户端连接到基于 Java 的 Axis2 Web 服务(不在我的控制范围内)。它即将应用 WS-Security,我需要修复 .NET 客户端。但是,我正在努力提供正确的身份验证。我知道 WSE 3.0 可能会使它变得更容易,但我不希望恢复到过时的技术。
类似问题(未解决),包括this、this 和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>
但是,我的看起来像这样:
<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>
我的客户看起来像这样: 附:请注意 必需 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);
当前绑定如下:
<basicHttpBinding>
<binding name="CustomBinding">
<security mode="TransportWithMessageCredential">
<transport clientCredentialType="None"></transport>
<message clientCredentialType="UserName" />
</security>
</binding>
</basicHttpBinding>
我也尝试了自定义绑定,得到了类似的错误:
<customBinding>
<binding name="myCustomBindingConfig">
<security authenticationMode="UserNameOverTransport"
messageSecurityVersion="WSSecurity11WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11"
securityHeaderLayout="Strict"
includeTimestamp="false"></security>
<textMessageEncoding messageVersion="Soap11"></textMessageEncoding>
<httpsTransport />
</binding>
</customBinding>
还有端点(地址明显变了……):
<endpoint address="https://www.somecompany.com/uat/axis/services/RemoteServiceOperation_Provider"
binding="basicHttpBinding" bindingConfiguration="CustomBinding"
contract="RemoteService.RemoteServicePortType"
name="RemoteService_UAT" />
返回的自定义故障如下:
<ErrorID>0</ErrorID>
<ErrorType>UNEXPECTED</ErrorType>
<ErrorDescription><![CDATA[Array index out of range: 0]]></ErrorDescription>
<TimeStamp>2010-06-23T13:28:54Z</TimeStamp>
我已经阅读了很多关于自定义标头、标记、绑定的内容,但我的大脑完全糊涂了。任何人都可以建议以正确格式发送消息的分步过程吗?
This 似乎是 WCF 的前进方向,使用自定义令牌,但是应该如何根据需要应用摘要和随机数?
欢迎任何帮助。
更新
我取得了一些有限的成功。我使用 Microsoft.Web.Services3 库创建了一个具有正确摘要的 UsernameToken。然后,我创建了自己的自定义行为,并在 BeforeSendRequest 方法中执行了以下操作来注入标头:
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;
我添加这样的行为:
CustomBehavior behavior = new CustomBehavior("USERNAME", "PASSWORD");
client.Endpoint.Behaviors.Add(behavior);
我现在可以看到标题:
<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>
但我得到了错误:
<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>
密码节点上似乎缺少 Type 属性:
Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest"
但是,我不确定安全跟踪和日志记录设置是否全面删除了这些节点的属性和内容。我尝试在诊断日志中使用logKnownPii 设置,但安全信息仍然模糊不清。有什么想法吗?
【问题讨论】:
【参考方案1】:我可以确认我的问题中的 UPDATE 确实有效:
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;
还有客户:
CustomBehavior behavior = new CustomBehavior("USERNAME", "PASSWORD");
client.Endpoint.Behaviors.Add(behavior);
错误消息无关。安全标头与一个非常简单的 basicHttpBinding 一起使用:
<basicHttpBinding>
<binding name="BasicSOAPBinding">
<security mode="Transport" />
</binding>
</basicHttpBinding>
【讨论】:
不幸的是,这看起来将来可能会消失。 ONVIF 规范现在说:“本标准中定义的服务应根据 [RFC 2617] 使用摘要式身份验证进行保护,但支持 [WS-UsernameToken] 的旧设备除外。”来源:onvif.org/specs/core/ONVIF-Core-Specification-v1706.pdf5.12.1【参考方案2】:这个问题写得很好——非常感谢。参考@Junto 的“我如何使用这个”评论,原来服务方法上的 SecurityHeader 参数可用于添加标头。我在下面提供了一个示例。我相信正在发生的事情是 SvcUtil.exe 工具在尝试读取 WS* DTD 时出现问题。当您使用“添加服务引用”向导时,这并不明显。但是当您从命令行运行 svcutil.exe 时,这一点非常明显。因为 svcutil.exe 无法读取 WS* DTD,所以 SecurityHeader 对象没有很好地开发。但是 Microsoft 为您提供了 .Any 属性。您可以将 UsernameToken 类直接序列化到 .Any 属性中,并且您的标头将添加到消息中。再次感谢您提出这个绝妙的问题。
如何使用SecurityHeader参数添加UsernameToken安全头:
所需工具:
Fiddler2(或类似的)——如果不检查,你真的无法弄清楚这些 http 标头。
必填参考:
Microsoft.Web.Services3.dll -- you can reference this 2.0 framework assembly from your 4.0 assembly
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:
<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】:我最近遇到了类似的问题,并放弃了寻找非 WSE 解决方案。几天后,我结束了下载 WSE 3.0 SDK,使用 WseWsdl3.exe 生成代理类,并为 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();
【讨论】:
以上是关于WCF 客户端使用 WS-Security UsernameToken PasswordDigest 身份验证方案使用 Axis 2 Web 服务时出错的主要内容,如果未能解决你的问题,请参考以下文章
WCF 客户端使用 WS-Security UsernameToken PasswordDigest 身份验证方案使用 Axis 2 Web 服务时出错
WCF SOAP 1.1 和 WS-Security 1.0、客户端证书传输身份验证、消息正文签名的服务证书、用户名令牌、密码摘要、随机数