简体   繁体   English

如何将标头中的用户名/密码传递给 SOAP WCF 服务

[英]How can I pass a username/password in the header to a SOAP WCF Service

I'm trying to consume a third-party web service https://staging.identitymanagement.lexisnexis.com/identity-proofing/services/identityProofingServiceWS/v2?wsdl我正在尝试使用第三方 Web 服务https://staging.identitymanagement.lexisnexis.com/identity-proofing/services/identityProofingServiceWS/v2?wsdl

I already added it as a service reference but I'm not sure how to pass the credentials for the header.我已经将它添加为服务引用,但我不确定如何传递标头的凭据。

How can I make the header request match this format?如何使标头请求匹配此格式?

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

The answers above are so wrong!楼上的答案都错了! DO NOT add custom headers.不要添加自定义标题。 Judging from your sample xml, it is a standard WS-Security header.从您的示例 xml 来看,它是一个标准的 WS-Security 标头。 WCF definitely supports it out of the box. WCF 绝对支持它开箱即用。 When you add a service reference you should have basicHttpBinding binding created for you in the config file.添加服务引用时,您应该在配置文件中为您创建 basicHttpBinding 绑定。 You will have to modify it to include security element with mode TransportWithMessageCredential and message element with clientCredentialType = UserName:您必须修改它以包含具有模式 TransportWithMessageCredential 的安全元素和具有 clientCredentialType = UserName 的消息元素:

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

The config above is telling WCF to expect userid/password in the SOAP header over HTTPS.上面的配置告诉 WCF 在 HTTPS 上的 SOAP 标头中期望用户 ID/密码。 Then you can set id/password in your code before making a call:然后,您可以在拨打电话之前在代码中设置 ID/密码:

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

Unless this particular service provider deviated from the standard, it should work.除非这个特定的服务提供商偏离标准,否则它应该可以工作。

There is probably a smarter way, but you can add the headers manually like this:可能有更聪明的方法,但您可以像这样手动添加标题:

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

Here, SecurityHeader is a custom implemented class, which needs a few other classes since I chose to use attributes to configure the XML serialization:这里, 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; }
}

I have not added the Nonce bit to the UsernameToken XML, but it is very similar to the Password one.我没有在UsernameToken XML 中添加Nonce位,但它与Password非常相似。 The Created element also needs to be added still, but it's a simple [XmlElement] .还需要添加Created元素,但它是一个简单的[XmlElement]

Adding a custom hard-coded header may work (it also may get rejected at times) but it is totally the wrong way to do it.添加自定义的硬编码标头可能会起作用(有时也可能会被拒绝),但这完全是错误的做法。 The purpose of the WSSE is security. WSSE 的目的是安全。 Microsoft released the Microsoft Web Services Enhancements 2.0 and subsequently the WSE 3.0 for this exact reason.正是出于这个原因,Microsoft 发布了 Microsoft Web Services Enhancements 2.0,随后发布了 WSE 3.0。 You need to install this package ( http://www.microsoft.com/en-us/download/details.aspx?id=14089 ).您需要安装此软件包 ( http://www.microsoft.com/en-us/download/details.aspx?id=14089 )。

The documentation is not easy to understand, especially for those who have not worked with SOAP and the WS-Addressing.该文档不容易理解,特别是对于那些没有使用过 SOAP 和 WS-Addressing 的人。 First of all the "BasicHttpBinding" is Soap 1.1 and it will not give you the same message header as the WSHttpBinding.首先,“BasicHttpBinding”是 Soap 1.1,它不会为您提供与 WSHttpBinding 相同的消息头。 Install the package and look at the examples.安装包并查看示例。 You will need to reference the DLL from WSE 3.0 and you will also need to setup your message correctly.您将需要从 WSE 3.0 引用 DLL,并且您还需要正确设置您的消息。 There are a huge number or variations on the WS Addressing header. WS Addressing 标头有大量的变化。 The one you are looking for is the UsernameToken configuration.您正在寻找的是 UsernameToken 配置。

This is a longer explanation and I should write something up for everyone since I cannot find the right answer anywhere.这是一个更长的解释,我应该为每个人写点东西,因为我在任何地方都找不到正确的答案。 At a minimum you need to start with the WSE 3.0 package.您至少需要从 WSE 3.0 包开始。

Answers that suggest that the header provided in the question are supported out of the box by WCF are incorrect.表明 WCF 开箱即用支持问题中提供的标题的答案不正确。 The header in the question contains a Nonce and a Created timestamp in the UsernameToken, which is an official part of the WS-Security specification that WCF does not support.问题中的标头在 Us​​ernameToken 中包含NonceCreated时间戳,这是 WCF 不支持的 WS-Security 规范的官方部分。 WCF only supports username and password out of the box . WCF 仅支持开箱即用的用户名和密码

If all you need to do is add a username and password, then Sergey's answer is the least-effort approach.如果您需要做的只是添加用户名和密码,那么 Sergey 的答案是最省力的方法。 If you need to add any other fields, you will need to supply custom classes to support them.如果需要添加任何其他字段,则需要提供自定义类来支持它们。

A somewhat more elegant approach that I found was to override the ClientCredentials, ClientCredentialsSecurityTokenManager and WSSecurityTokenizer classes to support the additional properties.发现一种更优雅的方法是覆盖 ClientCredentials、ClientCredentialsSecurityTokenManager 和 WSSecurityTokenizer 类以支持其他属性。 I've provided a link to the blog post where the approach is discussed in detail, but here is the sample code for the overrides:我已经提供了详细讨论该方法的博客文章的链接,但这里是覆盖的示例代码:

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

}

Before creating the client, you create the custom binding and manually add the security, encoding and transport elements to it.在创建客户端之前,您需要创建自定义绑定并向其手动添加安全性、编码和传输元素。 Then, replace the default ClientCredentials with your custom implementation and set the username and password as you would normally:然后,用您的自定义实现替换默认的 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;

Obviously it has been some years this post has been alive - but the fact is I did find it when looking for a similar issue.显然,这篇文章已经存在好几年了 - 但事实是我在寻找类似问题时确实找到了它。 In our case, we had to add the username / password info to the Security header.在我们的例子中,我们必须将用户名/密码信息添加到 Security 标头中。 This is different from adding header info outside of the Security headers.这与在 Security 标头之外添加标头信息不同。

The correct way to do this (for custom bindings / authenticationMode="CertificateOverTransport") (as on the .Net framework version 4.6.1), is to add the Client Credentials as usual :执行此操作的正确方法(对于自定义绑定/authenticationMode="CertificateOverTransport")(如 .Net 框架版本 4.6.1),是像往常一样添加客户端凭据:

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

and then add a "token" in the security binding element - as the username / pwd credentials would not be included by default when the authentication mode is set to certificate.然后在安全绑定元素中添加一个“令牌”——因为当身份验证模式设置为证书时,默认情况下不会包含用户名/密码凭据。

You can set this token like so:你可以像这样设置这个令牌:

    //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());

That should do it.那应该这样做。 Without the above code (to explicitly add the username token), even setting the username info in the client credentials may not result in those credentials passed to the Service.如果没有上述代码(显式添加用户名令牌),即使在客户端凭据中设置用户名信息也可能不会导致这些凭据传递给服务。

Suppose you have service reference of the name localhost in your web.config so you can go as follows假设您的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);

Suppose you are calling a web service using HttpWebRequest and HttpWebResponse, because .Net client doest support the structure of the WSLD that your are trying to consume.假设您正在使用 HttpWebRequest 和 HttpWebResponse 调用 Web 服务,因为 .Net 客户端不支持您尝试使用的 WSLD 结构。

In that case you can add the security credentials on the headers like:在这种情况下,您可以在标头上添加安全凭据,例如:

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

You can use SOAPUI to get the wsse Security, using the http log.您可以使用 SOAPUI 通过 http 日志获取 wsse 安全性。

Be careful because it is not a safe scenario.请小心,因为这不是一个安全的场景。

I got a better method from here: WCF: Creating Custom Headers, How To Add and Consume Those Headers我从这里得到了一个更好的方法: WCF:创建自定义标题,如何添加和使用这些标题

Client Identifies Itself客户识别自己
The goal here is to have the client provide some sort of information which the server can use to determine who is sending the message.这里的目标是让客户端提供某种信息,服务器可以使用这些信息来确定谁在发送消息。 The following C# code will add a header named ClientId:以下 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");

What that code is doing is adding an endpoint header named ClientId with a value of OmegaClient to be inserted into the soap header without a namespace.该代码所做的是添加一个名为 ClientId 的端点标头,其值为 OmegaClient 以插入到没有命名空间的soap标头中。

Custom Header in Client's Config File客户端配置文件中的自定义标题
There is an alternate way of doing a custom header.有另一种方式来做一个自定义标题。 That can be achieved in the Xml config file of the client where all messages sent by specifying the custom header as part of the endpoint as so:这可以在客户端的 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>

I added customBinding to the web.config.我在 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>

After adding customBinding, I can pass username and password to client service like as follows:添加 customBinding 后,我可以将用户名和密码传递给客户端服务,如下所示:

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

In this way you can pass username, password in the header to a SOAP WCF Service.通过这种方式,您可以将标头中的用户名、密码传递给 SOAP WCF 服务。

If this is in relation to this Peoplesoft issue: https://support.oracle.com/knowledge/PeopleSoft%20Enterprise/2370907_1.html如果这与 Peoplesoft 问题有关: https : //support.oracle.com/knowledge/PeopleSoft%20Enterprise/2370907_1.html

I needed to set the attribute on the Soap Password, where previously the attribute was not being set on that tag.我需要在 Soap 密码上设置属性,而以前没有在该标签上设置该属性。

I simply set the MessageSecurityVersion on my Custom Binding:我只是在我的自定义绑定上设置 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;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM