【问题标题】:WCF: Adding Nonce to UsernameTokenWCF:将 Nonce 添加到 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 应用于服务请求,我认为就是这样。

标签: c# wcf web-services


【解决方案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&lt;ClientCredentials&gt;(); 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】:

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

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

如果您想了解我的解决方案进度,您可以在 StackOverflow 上找到标题为“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);
    }
}

其他标签,例如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;
}

【讨论】:

    猜你喜欢
    • 2011-11-17
    • 2018-10-04
    • 2015-03-14
    • 1970-01-01
    • 2020-11-20
    • 2011-05-16
    • 2020-10-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多