【问题标题】:How can I pass a username/password in the header to a SOAP WCF Service如何将标头中的用户名/密码传递给 SOAP WCF 服务
【发布时间】:2013-04-08 07:47:59
【问题描述】:

我正在尝试使用第三方网络服务 https://staging.identitymanagement.lexisnexis.com/identity-proofing/services/identityProofingServiceWS/v2?wsdl

我已经将它添加为服务参考,但我不确定如何传递标头的凭据。

如何使标头请求匹配这种格式?

<soapenv:Header>
    <wsse:Security soapenv:mustUnderstand="1" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
        <wsse:UsernameToken wsu:Id="UsernameToken-49" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
            <wsse:Username>12345/userID</wsse:Username>
            <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/ oasis-200401-wss-username-token-profile-1.0#PasswordText">password123</wsse:Password>
            <wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">d+VxCZX1cH/ieMkKEr/ofA==</wsse:Nonce>
            <wsu:Created>2012-08-04T20:25:04.038Z</wsu:Created>
        </wsse:UsernameToken>
    </wsse:Security>
</soapenv:Header>

【问题讨论】:

标签: c# asp.net wcf web-services soap


【解决方案1】:

上面的答案太错误了!不要添加自定义标题。从您的示例 xml 来看,它是一个标准的 WS-Security 标头。 WCF 绝对支持开箱即用。添加服务引用时,您应该在配置文件中为您创建 basicHttpBinding 绑定。您必须对其进行修改以包含具有模式 TransportWithMessageCredential 的安全元素和具有 clientCredentialType = UserName 的消息元素:

<basicHttpBinding>
  <binding name="usernameHttps">
    <security mode="TransportWithMessageCredential">
      <message clientCredentialType="UserName"/>
    </security>
  </binding>
</basicHttpBinding>

上面的配置告诉 WCF 在 HTTPS 的 SOAP 标头中期望用户 ID/密码。然后您可以在拨打电话之前在代码中设置 ID/密码:

var service = new MyServiceClient();
service.ClientCredentials.UserName.UserName = "username";
service.ClientCredentials.UserName.Password = "password";

除非这个特定的服务提供商偏离标准,否则它应该可以工作。

【讨论】:

  • 谢谢,这将是一个干净的解决方案。我收到此错误消息:client.ClientCredentials.UserName.UserName = "12345/userID"; System.InvalidOperationException:对象是只读的。
  • 这应该会有所帮助。 stackoverflow.com/questions/199014/…
  • 感谢您解决了该错误,但现在我收到此错误 处理 标头时发现错误有什么想法吗?谢谢
  • 啊哈,看起来它不喜欢 WCF 添加的额外标签。用自定义绑定替换基本绑定,并更改您的端点以引用它,它可以防止 WCF 添加时间戳标记,还允许接收不安全的错误。 httpsTransport>
  • 我对答案投了反对票,因为问题中的示例专门列出了用户名令牌中的随机数和创建的时间戳,这是 WS-Security 规范的一部分,但 WCF 不支持的盒子。它需要自定义 ClientCredentials 和几个其他类的覆盖。我在这里找到了有关如何执行此操作的更多信息,但由于我自己还没有测试过,所以还没有添加作为答案:weblog.west-wind.com/posts/2012/nov/24/…
【解决方案2】:

可能有更聪明的方法,但您可以像这样手动添加标题:

var client = new IdentityProofingService.IdentityProofingWSClient();

using (new OperationContextScope(client.InnerChannel))
{
    OperationContext.Current.OutgoingMessageHeaders.Add(
        new SecurityHeader("UsernameToken-49", "12345/userID", "password123"));
    client.invokeIdentityService(new IdentityProofingRequest());
}

这里,SecurityHeader是一个自定义实现的类,由于我选择使用属性来配置XML序列化,因此需要一些其他的类:

public class SecurityHeader : MessageHeader
{
    private readonly UsernameToken _usernameToken;

    public SecurityHeader(string id, string username, string password)
    {
        _usernameToken = new UsernameToken(id, username, password);
    }

    public override string Name
    {
        get { return "Security"; }
    }

    public override string Namespace
    {
        get { return "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"; }
    }

    protected override void OnWriteHeaderContents(XmlDictionaryWriter writer, MessageVersion messageVersion)
    {
        XmlSerializer serializer = new XmlSerializer(typeof(UsernameToken));
        serializer.Serialize(writer, _usernameToken);
    }
}


[XmlRoot(Namespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd")]
public class UsernameToken
{
    public UsernameToken()
    {
    }

    public UsernameToken(string id, string username, string password)
    {
        Id = id;
        Username = username;
        Password = new Password() {Value = password};
    }

    [XmlAttribute(Namespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd")]
    public string Id { get; set; }

    [XmlElement]
    public string Username { get; set; }

    [XmlElement]
    public Password Password { get; set; }
}

public class Password
{
    public Password()
    {
        Type = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText";
    }

    [XmlAttribute]
    public string Type { get; set; }

    [XmlText]
    public string Value { get; set; }
}

我没有将Nonce 位添加到UsernameToken XML,但它与Password 非常相似。 Created 元素还需要添加,但它是一个简单的[XmlElement]

【讨论】:

  • 我希望完全控制我的 WCF SOAP 标头,因为我的客户没有完全遵循 WS-* 标准。这是这种情况下的完美解决方案!谢谢。
  • 最后我找到了这个解决方案,效果很好。非常感谢。
  • @Oguzhan KIRCALI - 你能建议我如何解决同样的问题吗?
  • 您好,如果您调用服务,您可以在端点中尝试此标头。让我知道你的尝试结果。 @AbhishekTomar docs.oasis-open.org/wss/2004/01/…"> docs.oasis-open.org/wss/2004/01/…"> yourUsername用户名> 2
  • @Oguzhan KIRCALI ---- Error : System.ServiceModel.ProtocolException: '响应消息的内容类型 application/json;charset=UTF-8 与绑定的内容类型不匹配(应用程序/soap+xml;字符集=utf-8)。如果使用自定义编码器,请确保正确实现 IsContentTypeSupported 方法。响应的前 474 个字节是:'schemas.xmlsoap.org/soap/envelope">
【解决方案3】:

添加自定义的硬编码标头可能会起作用(有时也可能会被拒绝),但这完全是错误的做法。 WSSE 的目的是安全。正是出于这个原因,Microsoft 发布了 Microsoft Web Services Enhancements 2.0 和随后的 WSE 3.0。您需要安装此软件包 (http://www.microsoft.com/en-us/download/details.aspx?id=14089)。

文档不容易理解,特别是对于那些没有使用过 SOAP 和 WS-Addressing 的人。首先,“BasicHttpBinding”是 Soap 1.1,它不会为您提供与 WSHttpBinding 相同的消息头。安装软件包并查看示例。您将需要从 WSE 3.0 引用 DLL,并且您还需要正确设置您的消息。 WS Addressing 标头有大量或变体。您正在寻找的是 UsernameToken 配置。

这是一个较长的解释,我应该为大家写一些东西,因为我在任何地方都找不到正确的答案。您至少需要从 WSE 3.0 包开始。

【讨论】:

    【解决方案4】:

    很明显,这篇文章已经存在几年了 - 但事实是我在寻找类似问题时确实找到了它。在我们的例子中,我们必须将用户名/密码信息添加到 Security 标头中。这与在安全标头之外添加标头信息不同。

    执行此操作的正确方法(对于自定义绑定 / authenticationMode="CertificateOverTransport")(如在 .Net 框架版本 4.6.1 上)是照常添加客户端凭据:

        client.ClientCredentials.UserName.UserName = "[username]";
        client.ClientCredentials.UserName.Password = "[password]";
    

    然后在安全绑定元素中添加一个“令牌” - 因为当身份验证模式设置为证书时,默认情况下不会包含用户名/密码凭据。

    你可以像这样设置这个令牌:

        //Get the current binding 
        System.ServiceModel.Channels.Binding binding = client.Endpoint.Binding;
        //Get the binding elements 
        BindingElementCollection elements = binding.CreateBindingElements();
        //Locate the Security binding element
        SecurityBindingElement security = elements.Find<SecurityBindingElement>();
    
        //This should not be null - as we are using Certificate authentication anyway
        if (security != null)
        {
        UserNameSecurityTokenParameters uTokenParams = new UserNameSecurityTokenParameters();
        uTokenParams.InclusionMode = SecurityTokenInclusionMode.AlwaysToRecipient;
    security.EndpointSupportingTokenParameters.SignedEncrypted.Add(uTokenParams);
        }
    
       client.Endpoint.Binding = new CustomBinding(elements.ToArray());
    

    应该这样做。如果没有上述代码(显式添加用户名令牌),即使在客户端凭据中设置用户名信息也可能不会导致这些凭据传递给服务。

    【讨论】:

      【解决方案5】:

      建议问题中提供的标头得到 WCF 开箱即用的支持的答案是不正确的。问题的标头在 Us​​ernameToken 中包含 NonceCreated 时间戳,这是 WCF 不支持的 WS-Security 规范的官方部分。 WCF 仅支持开箱即用的用户名和密码

      如果您需要做的只是添加用户名和密码,那么 Sergey 的答案是最省力的方法。如果您需要添加任何其他字段,则需要提供自定义类来支持它们。

      found 的一种更优雅的方法是覆盖 ClientCredentials、ClientCredentialsSecurityTokenManager 和 WSSecurityTokenizer 类以支持其他属性。我提供了博客文章的链接,其中详细讨论了该方法,但这里是覆盖的示例代码:

      public class CustomCredentials : ClientCredentials
      {
          public CustomCredentials()
          { }
      
          protected CustomCredentials(CustomCredentials cc)
              : base(cc)
          { }
      
          public override System.IdentityModel.Selectors.SecurityTokenManager CreateSecurityTokenManager()
          {
              return new CustomSecurityTokenManager(this);
          }
      
          protected override ClientCredentials CloneCore()
          {
              return new CustomCredentials(this);
          }
      }
      
      public class CustomSecurityTokenManager : ClientCredentialsSecurityTokenManager
      {
          public CustomSecurityTokenManager(CustomCredentials cred)
              : base(cred)
          { }
      
          public override System.IdentityModel.Selectors.SecurityTokenSerializer CreateSecurityTokenSerializer(System.IdentityModel.Selectors.SecurityTokenVersion version)
          {
              return new CustomTokenSerializer(System.ServiceModel.Security.SecurityVersion.WSSecurity11);
          }
      }
      
      public class CustomTokenSerializer : WSSecurityTokenSerializer
      {
          public CustomTokenSerializer(SecurityVersion sv)
              : base(sv)
          { }
      
          protected override void WriteTokenCore(System.Xml.XmlWriter writer,
                                                  System.IdentityModel.Tokens.SecurityToken token)
          {
              UserNameSecurityToken userToken = token as UserNameSecurityToken;
      
              string tokennamespace = "o";
      
              DateTime created = DateTime.Now;
              string createdStr = created.ToString("yyyy-MM-ddTHH:mm:ss.fffZ");
      
              // unique Nonce value - encode with SHA-1 for 'randomness'
              // in theory the nonce could just be the GUID by itself
              string phrase = Guid.NewGuid().ToString();
              var nonce = GetSHA1String(phrase);
      
              // in this case password is plain text
              // for digest mode password needs to be encoded as:
              // PasswordAsDigest = Base64(SHA-1(Nonce + Created + Password))
              // and profile needs to change to
              //string password = GetSHA1String(nonce + createdStr + userToken.Password);
      
              string password = userToken.Password;
      
              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>" + userToken.UserName + "</{0}:Username>" +
              "<{0}:Password Type=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText\">" +
              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 GetSHA1String(string phrase)
          {
              SHA1CryptoServiceProvider sha1Hasher = new SHA1CryptoServiceProvider();
              byte[] hashedDataBytes = sha1Hasher.ComputeHash(Encoding.UTF8.GetBytes(phrase));
              return Convert.ToBase64String(hashedDataBytes);
          }
      
      }
      

      在创建客户端之前,您需要创建自定义绑定并向其手动添加安全、编码和传输元素。然后,将默认 ClientCredentials 替换为您的自定义实现,并像往常一样设置用户名和密码:

      var security = TransportSecurityBindingElement.CreateUserNameOverTransportBindingElement();
          security.IncludeTimestamp = false;
          security.DefaultAlgorithmSuite = SecurityAlgorithmSuite.Basic256;
          security.MessageSecurityVersion = MessageSecurityVersion.WSSecurity10WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10;
      
      var encoding = new TextMessageEncodingBindingElement();
      encoding.MessageVersion = MessageVersion.Soap11;
      
      var transport = new HttpsTransportBindingElement();
      transport.MaxReceivedMessageSize = 20000000; // 20 megs
      
      binding.Elements.Add(security);
      binding.Elements.Add(encoding);
      binding.Elements.Add(transport);
      
      RealTimeOnlineClient client = new RealTimeOnlineClient(binding,
          new EndpointAddress(url));
      
          client.ChannelFactory.Endpoint.EndpointBehaviors.Remove(client.ClientCredentials);
      client.ChannelFactory.Endpoint.EndpointBehaviors.Add(new CustomCredentials());
      
      client.ClientCredentials.UserName.UserName = username;
      client.ClientCredentials.UserName.Password = password;
      

      【讨论】:

        【解决方案6】:

        假设您的web.config 中有名称为localhost 的服务引用,那么您可以如下进行

        localhost.Service objWebService = newlocalhost.Service();
        localhost.AuthSoapHd objAuthSoapHeader = newlocalhost.AuthSoapHd();
        string strUsrName =ConfigurationManager.AppSettings["UserName"];
        string strPassword =ConfigurationManager.AppSettings["Password"];
        
        objAuthSoapHeader.strUserName = strUsrName;
        objAuthSoapHeader.strPassword = strPassword;
        
        objWebService.AuthSoapHdValue =objAuthSoapHeader;
        string str = objWebService.HelloWorld();
        
        Response.Write(str);
        

        【讨论】:

        • 这不是 WCF,而是旧式 Web 服务。
        • Thorarin,你是说我不能使用 wcf 使用 Web 服务吗?我必须将参考添加为网络参考?
        【解决方案7】:

        假设您正在使用 HttpWebRequest 和 HttpWebResponse 调用 Web 服务,因为 .Net 客户端不支持您尝试使用的 WSLD 的结构。

        在这种情况下,您可以在标题中添加安全凭据,例如:

        <soap:Envelpe>
        <soap:Header>
            <wsse:Security soap:mustUnderstand='true' 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:UsernameToken wsu:Id='UsernameToken-3DAJDJSKJDHFJASDKJFKJ234JL2K3H2K3J42'><wsse:Username>YOU_USERNAME/wsse:Username><wsse:Password Type='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText'>YOU_PASSWORD</wsse:Password><wsse:Nonce EncodingType='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary'>3WSOKcKKm0jdi3943ts1AQ==</wsse:Nonce><wsu:Created>2015-01-12T16:46:58.386Z</wsu:Created></wsse:UsernameToken></wsse:Security>
        </soapHeather>
        <soap:Body>
        </soap:Body>
        
        
        </soap:Envelope>
        

        您可以使用 SOAPUI 获取 wsse 安全性,使用 http 日志。

        小心,因为这不是一个安全的场景。

        【讨论】:

          【解决方案8】:

          我从这里得到了一个更好的方法:WCF: Creating Custom Headers, How To Add and Consume Those Headers

          客户标识自己
          这里的目标是让客户提供某种信息 服务器可以使用它来确定谁在发送消息。这 以下 C# 代码将添加一个名为 ClientId 的标头:

          var cl = new ActiveDirectoryClient();
          
          var eab = new EndpointAddressBuilder(cl.Endpoint.Address);
          
          eab.Headers.Add( AddressHeader.CreateAddressHeader("ClientId",       // Header Name
                                                             string.Empty,     // Namespace
                                                              "OmegaClient")); // Header Value
          cl.Endpoint.Address = eab.ToEndpointAddress();
          
          // Now do an operation provided by the service.
          cl.ProcessInfo("ABC");
          

          该代码所做的是添加一个名为 ClientId 的端点标头 将值 OmegaClient 插入到 soap 标头中 没有命名空间。

          客户端配置文件中的自定义标题
          有另一种方法 做一个自定义标题。这可以在 Xml 配置文件中实现 通过将自定义标头指定为发送所有消息的客户端 端点的一部分是这样的:

          <configuration>
              <startup> 
                  <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
              </startup>
              <system.serviceModel>
                  <bindings>
                      <basicHttpBinding>
                          <binding name="BasicHttpBinding_IActiveDirectory" />
                      </basicHttpBinding>
                  </bindings>
                  <client>
                    <endpoint address="http://localhost:41863/ActiveDirectoryService.svc"
                        binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IActiveDirectory"
                        contract="ADService.IActiveDirectory" name="BasicHttpBinding_IActiveDirectory">
                      <headers>
                        <ClientId>Console_Client</ClientId>
                      </headers>
                    </endpoint>
                  </client>
              </system.serviceModel>
          </configuration>
          

          【讨论】:

            【解决方案9】:

            我在 web.config 中添加了 customBinding。

            <configuration>
              <system.serviceModel>
                <bindings>
                  <customBinding>
                    <binding name="CustomSoapBinding">
                      <security includeTimestamp="false"
                                authenticationMode="UserNameOverTransport"
                                defaultAlgorithmSuite="Basic256"
                                requireDerivedKeys="false"
                                messageSecurityVersion="WSSecurity10WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10">
                      </security>
                      <textMessageEncoding messageVersion="Soap11"></textMessageEncoding>
                      <httpsTransport maxReceivedMessageSize="2000000000"/>
                    </binding>
                  </customBinding>
                </bindings>
                <client>
                  <endpoint address="https://test.com:443/services/testService"
                            binding="customBinding"
                            bindingConfiguration="CustomSoapBinding"
                            contract="testService.test"
                            name="test" />
                </client>
              </system.serviceModel>
              <startup>
                <supportedRuntime version="v4.0"
                                  sku=".NETFramework,Version=v4.0"/>
              </startup>
            </configuration>
            

            添加customBinding后,我可以将用户名和密码传递给客户端服务,如下所示:

            service.ClientCridentials.UserName.UserName = "testUser";
            service.ClientCridentials.UserName.Password = "testPass";
            

            通过这种方式,您可以将标头中的用户名、密码传递给 SOAP WCF 服务。

            【讨论】:

              【解决方案10】:

              如果这与此 Peoplesoft 问题有关:https://support.oracle.com/knowledge/PeopleSoft%20Enterprise/2370907_1.html

              我需要在 Soap 密码上设置属性,而之前没有在该标签上设置属性。

              我只是在我的自定义绑定上设置 MessageSecurityVersion:

                CustomBinding customBinding = new CustomBinding();
                          customBinding.Name = endpointName;
                          customBinding.CloseTimeout = TimeSpan.FromMinutes(1);
                          customBinding.OpenTimeout = TimeSpan.FromMinutes(1);
                          customBinding.SendTimeout = TimeSpan.FromMinutes(20);
                          customBinding.ReceiveTimeout = TimeSpan.FromMinutes(20);
              
                          TextMessageEncodingBindingElement textMessageElement = new TextMessageEncodingBindingElement(MessageVersion.Soap11, Encoding.UTF8);
                          customBinding.Elements.Add(textMessageElement);
              
                          TransportSecurityBindingElement securityElement = SecurityBindingElement.CreateUserNameOverTransportBindingElement();
                          securityElement.IncludeTimestamp = false;
                          securityElement.MessageSecurityVersion = MessageSecurityVersion.WSSecurity10WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10;
                          customBinding.Elements.Add(securityElement);
              
                          // ORDER MATTERS: THIS HAS TO BE LAST!!! - HVT
                          HttpsTransportBindingElement transportElement = new HttpsTransportBindingElement();
                          transportElement.MaxBufferSize = int.MaxValue;
                          transportElement.MaxReceivedMessageSize = int.MaxValue;
                          customBinding.Elements.Add(transportElement);
              
                          return customBinding;
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2012-07-17
                • 2015-11-11
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多