我如何在头中传递用户名/密码给SOAP WCF服务

本文关键字:密码 SOAP 服务 WCF 用户 | 更新日期: 2023-09-27 18:09:35

我正在尝试使用第三方web服务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>

我如何在头中传递用户名/密码给SOAP WCF服务

以上答案都是错误的!不要添加自定义标题。从您的样例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/password:

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

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

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

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

添加自定义硬编码头可能工作(它也可能被拒绝),但这是完全错误的方式去做它。WSSE的目的是安全性。正是出于这个原因,微软发布了微软Web服务增强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寻址头有大量的变化。您正在寻找的是UsernameToken配置。

这是一个较长的解释,我应该为每个人写一些东西,因为我找不到正确的答案。您至少需要从WSE 3.0包开始。

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

正确的方法(对于自定义绑定/authenticationMode="CertificateOverTransport")(如。net框架4.6.1),是像往常一样添加客户端凭据:

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

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

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

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

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

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

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

我发现的一种更优雅的方法是覆盖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;

假设您的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);

假设您正在使用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日志。

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

我从这里得到了一个更好的方法:WCF:创建自定义头,如何添加和使用这些头

客户端标识自身
这里的目标是让客户机提供某种类型的信息服务器可以使用它来确定谁在发送消息。的下面的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的端点头在soap报头中插入一个OmegaClient值没有命名空间

客户端配置文件中的自定义头
还有另一种方法做一个自定义标题。的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>

我在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服务。

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

我需要在Soap Password上设置属性,在此之前该属性没有在该标记上设置。

我只是在我的自定义绑定上设置了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;