WCF: Adding Nonce to UsernameToken

后端 未结 5 381
心在旅途
心在旅途 2020-12-01 06:22

I\'m trying to connect to a web service, written in Java, but there\'s something I can\'t figure out.

Using WCF and a customBinding, almost everything seems to be fi

相关标签:
5条回答
  • 2020-12-01 07:00

    This article provides sample with full integration of UserNameToken Profile with digested password into WCF security pipeline.

    0 讨论(0)
  • 2020-12-01 07:15

    To create the nonce, I had to change a few things

    First, added a custom binding in my config

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

    Then, take this code found here: http://social.msdn.microsoft.com/Forums/en-US/wcf/thread/4df3354f-0627-42d9-b5fb-6e880b60f8ee and modify it to create the nonce (just a random hash, base-64 encoded)

    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);
    }
    
    0 讨论(0)
  • 2020-12-01 07:17

    I also had to put a UserNameHeader segment in the SOAP message header:

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

    This was accomplished with a custom message header:

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

    Other tags, such as Nonce and Created, could easily be added.

    The class is used as follows:

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

    Note: MessageSigningCertificate is an X.509 certificate, I read it from a file:

    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;
    }
    
    0 讨论(0)
  • 2020-12-01 07:18

    I had the same problem. Instead of the custom token serializer I used a MessageInspector to add the correct UsernameToken in the BeforeSendRequest method. I then used a custom behavior to apply the fix.

    The entire process is documented (with a demo project) in my blog post Supporting the WS-I Basic Profile Password Digest in a WCF client proxy. Alternatively, you can just read the PDF.

    If you want to follow my progress through to the solution, you'll find it on StackOverflow titled, "Error in WCF client consuming Axis 2 web service with WS-Security UsernameToken PasswordDigest authentication scheme":

    0 讨论(0)
  • 2020-12-01 07:18

    It's worth pointing out that Rick Strahl made a blog post (which he references this question) where he explains it all quite clearly and offers solutions for both just Password and also PasswordDigest.

    I post this because I found this article originally, couldn't really follow it, and found Rick's post much later. This might save some people some time.

    WCF WSSecurity and WSE Nonce Authentication

    0 讨论(0)
提交回复
热议问题