WCF:将Nonce添加到UsernameToken

时间:2009-05-22 08:38:04

标签: c# wcf web-services

我正在尝试连接到用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元素?

5 个答案:

答案 0 :(得分:21)

要创建现时,我不得不改变一些事情

首先,在我的配置中添加了自定义绑定

<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);
}

答案 1 :(得分:11)

我遇到了同样的问题。我使用MessageInspector代替自定义令牌序列化程序,在UsernameToken方法中添加了正确的BeforeSendRequest。然后我使用自定义行为来应用修复。

我的博文demo project中记录了整个过程(Supporting the WS-I Basic Profile Password Digest in a WCF client proxy}。或者,您可以阅读PDF

如果您想跟进我的进度到解决方案,您可以在StackOverflow上找到标题为“Error in WCF client consuming Axis 2 web service with WS-Security UsernameToken PasswordDigest authentication scheme”:

答案 2 :(得分:7)

This article提供了将UserNameToken Profile与消化密码完全集成到WCF安全管道中的示例。

答案 3 :(得分:7)

值得指出的是,Rick Strahl撰写了一篇博文(他引用了这个问题),在那里他非常清楚地解释了这一点,并为密码和PasswordDigest提供了解决方案。

我发布这篇文章是因为我最初发现这篇文章,并不能真正遵循它,并且很晚才找到Rick的帖子。这可能会节省一些人的时间。

WCF WSSecurity and WSE Nonce Authentication

答案 4 :(得分:-1)

我还必须在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);
    }
}

可以轻松添加其他代码,例如NonceCreated

该类使用如下:

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;
}