WCF:将 Nonce 添加到 UsernameToken
Posted
技术标签:
【中文标题】WCF:将 Nonce 添加到 UsernameToken【英文标题】:WCF: Adding Nonce to UsernameToken 【发布时间】:2010-10-28 04:03:23 【问题描述】:我正在尝试连接到一个用 Java 编写的 Web 服务,但有些东西我想不通。
使用 WCF 和 customBinding,除了 SOAP 消息的一部分之外,几乎一切似乎都很好,因为它缺少 Nonce 和 Created 部分节点。 显然我错过了一些东西,所以如果你能指出我正确的方向,那将不胜感激。
这是自定义绑定:
<binding name="CustomHTTPBinding">
<security includeTimestamp="false" authenticationMode="UserNameOverTransport" defaultAlgorithmSuite="Basic256" requireDerivedKeys="True"
messageSecurityVersion="WSSecurity10WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10">
</security>
<textMessageEncoding maxReadPoolSize="211" maxWritePoolSize="2132" messageVersion="Soap11"
writeEncoding="utf-8"/>
<httpsTransport />
</binding>
这是消息的相关部分:
<o:Security s:mustUnderstand="1" xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<o:UsernameToken u:Id="uuid-c306efd1-e84c-410e-a2ad-1046b368582e-1">
<o:Username>
<!-- Removed-->
</o:Username>
<o:Password>
<!-- Removed-->
</o:Password>
</o:UsernameToken>
</o:Security>
它应该是这样的:
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" soapenv:mustUnderstand="1">
<wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="UsernameToken-25763165">
<wsse:Username>..</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">..</wsse:Password>
<wsse:Nonce>6ApOnLn5Aq9KSH46pzzcZA==</wsse:Nonce>
<wsu:Created>2009-05-13T18:59:23.309Z</wsu:Created>
</wsse:UsernameToken>
</wsse:Security>
所以问题是:如何在安全部分中引入 Nonce 和 Created 元素?
【问题讨论】:
您找到解决方案了吗?我很想知道。 最后我们使用 WSE 2 来解决我们遇到的问题,而不是 WCF。在那里,我们添加了一个自定义策略以将 UsernameToken 应用于服务请求,我认为就是这样。 【参考方案1】:要创建随机数,我必须更改一些内容
首先,在我的配置中添加自定义绑定
<system.serviceModel>
<bindings>
<customBinding>
<binding name="myCustomBindingConfig">
<security includeTimestamp="false"
authenticationMode="UserNameOverTransport"
defaultAlgorithmSuite="Basic256"
requireDerivedKeys="true"
messageSecurityVersion="WSSecurity10WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10">
</security>
<textMessageEncoding messageVersion="Soap11"></textMessageEncoding>
<httpsTransport maxReceivedMessageSize="2000000000" />
</binding>
</customBinding>
</bindings>
</system.serviceModel>
<client>
<endpoint address="https://..." [other tags]
binding="customBinding" bindingConfiguration="OrangeLeapCustomBindingConfig"/>
</client>
然后,获取这里的代码:http://social.msdn.microsoft.com/Forums/en-US/wcf/thread/4df3354f-0627-42d9-b5fb-6e880b60f8ee 并修改它以创建随机数(只是一个随机哈希,base-64 编码)
protected override void WriteTokenCore(System.Xml.XmlWriter writer, System.IdentityModel.Tokens.SecurityToken token)
Random r = new Random();
string tokennamespace = "o";
DateTime created = DateTime.Now;
string createdStr = created.ToString("yyyy-MM-ddTHH:mm:ss.fffZ");
string nonce = Convert.ToBase64String(Encoding.ASCII.GetBytes(SHA1Encrypt(created + r.Next().ToString())));
System.IdentityModel.Tokens.UserNameSecurityToken unToken = (System.IdentityModel.Tokens.UserNameSecurityToken)token;
writer.WriteRaw(String.Format(
"<0:UsernameToken u:Id=\"" + token.Id + "\" xmlns:u=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\">" +
"<0:Username>" + unToken.UserName + "</0:Username>" +
"<0:Password Type=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText\">" +
unToken.Password + "</0:Password>" +
"<0:Nonce EncodingType=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary\">" +
nonce + "</0:Nonce>" +
"<u:Created>" + createdStr + "</u:Created></0:UsernameToken>", tokennamespace));
protected String ByteArrayToString(byte[] inputArray)
StringBuilder output = new StringBuilder("");
for (int i = 0; i < inputArray.Length; i++)
output.Append(inputArray[i].ToString("X2"));
return output.ToString();
protected String SHA1Encrypt(String phrase)
UTF8Encoding encoder = new UTF8Encoding();
SHA1CryptoServiceProvider sha1Hasher = new SHA1CryptoServiceProvider();
byte[] hashedDataBytes = sha1Hasher.ComputeHash(encoder.GetBytes(phrase));
return ByteArrayToString(hashedDataBytes);
【讨论】:
感谢您的回答。我暂时没有资格试一试,但看起来还可以,所以我会接受。 可能是一个旧答案,但它可能解决了我遇到的一个问题,与 Java 商店的网络服务交谈!谢谢!缺少的部分是在链接的 Microsoft 页面中,添加自定义行为后,必须设置用户名和密码(在 service.ClientCredentials.UserName 字段中) @JohnT:我想我错过了那一个缺失的部分。我正在执行以下操作:mhsClient.ChannelFactory.Endpoint.Behaviors.Remove<ClientCredentials>(); mhsClient.ChannelFactory.Endpoint.Behaviors.Add(new CustomCredentials()); mhsClient.ClientCredentials.UserName.UserName = "username"; mhsClient.ClientCredentials.UserName.Password = "password";
,我收到以下错误:"Text cannot be written outside the root element"
。
我正在使用的服务没有抱怨丢失随机数,所以我把它排除在外。我已经描述了我的工作细节here。
@IsaacKleinman:啊。不幸的是,我需要它,但感谢您的文章!老实说,我没想到会有任何回应,而且绝对不会这么快:D【参考方案2】:
我遇到了同样的问题。我使用MessageInspector
在BeforeSendRequest
方法中添加正确的UsernameToken
,而不是自定义令牌序列化程序。然后我使用自定义行为来应用修复。
整个过程记录在我的博文Supporting the WS-I Basic Profile Password Digest in a WCF client proxy 中(带有demo project)。或者,您可以阅读PDF。
如果您想了解我的解决方案进度,您可以在 *** 上找到标题为“Error in WCF client consuming Axis 2 web service with WS-Security UsernameToken PasswordDigest authentication scheme”的解决方案:
【讨论】:
谢谢你,你拯救了我的一天。 @Sebacote 很高兴为您提供帮助。开始工作很痛苦,所以我想我会分享我的经验。【参考方案3】:值得指出的是,Rick Strahl 发表了一篇博文(他引用了这个问题),他非常清楚地解释了这一切,并为 Password 和 PasswordDigest 提供了解决方案。
我发布这篇文章是因为我最初发现这篇文章,无法真正关注它,并且在很久以后才找到 Rick 的帖子。这可能会为一些人节省一些时间。
WCF WSSecurity and WSE Nonce Authentication
【讨论】:
以上答案为 SOAP 初学者遗漏了几个步骤,本文给出了逐步解决此问题的方法并解释了沿途的每一步。 这正是我想要的。此处接受的答案并未说明将代码放在何处,而是指向 Rick Strahl 的博文。赞成这个答案。【参考方案4】:我还必须在 SOAP 消息头中放置一个 UserNameHeader 段:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:bar:services" xmlns:efm="urn:bar:services">
<soapenv:Header>
<efm:UserNameHeader>
<UserName>foouser</UserName>
<Password>foopass</Password>
</efm:UserNameHeader>
</soapenv:Header>
<soapenv:Body>
<urn:GetUserList/>
</soapenv:Body>
</soapenv:Envelope>
这是通过自定义消息头完成的:
public class UserNamePasswordHeader : MessageHeader
private readonly string _serviceUserEmail;
private readonly string _serviceUserPassword;
public UserNamePasswordHeader(string serviceUserEmail, string serviceUserPassword)
this._serviceUserEmail = serviceUserEmail;
this._serviceUserPassword = serviceUserPassword;
public override string Name
get return "UserNameHeader";
public override string Namespace
get return "urn:bar:services";
protected override void OnWriteHeaderContents(XmlDictionaryWriter writer, MessageVersion messageVersion)
writer.WriteElementString("UserName", _serviceUserEmail);
writer.WriteElementString("Password", _serviceUserPassword);
其他标签,例如Nonce
和Created
,可以很容易地添加。
类使用如下:
var service = new BarServiceClient();
service.ClientCredentials.ClientCertificate.Certificate = MessageSigningCertificate;
using (new OperationContextScope(service.InnerChannel))
OperationContext.Current.OutgoingMessageHeaders.Add(
new UserNamePasswordHeader(serviceUserEmail, serviceUserPassword));
try
var response = service.GetUserList();
return response;
finally
service.Close();
注意:MessageSigningCertificate 是 X.509 证书,我是从文件中读取的:
private static X509Certificate2 LoadCertificateFromFile(string pfxFilePath, string privateKeyPassword)
// Load the certificate from a file, specifying the password
var certificate = new X509Certificate2(pfxFilePath, privateKeyPassword);
return certificate;
【讨论】:
以上是关于WCF:将 Nonce 添加到 UsernameToken的主要内容,如果未能解决你的问题,请参考以下文章
在 Web 场环境中使用共享缓存来检测 WCF 中的重放攻击