Pergunta

This question has been discussed in several topics here but I could not find the answer for me.

What I'm trying to do is use an IP camera through the Onvif interface. I've generated the web services from the WSDL files available in the Onvif homepage, and added the custom SOAP authentication code as suggested here, and I am able to retrieve the device capabilities etc. etc.

But for some services, e.g, PTZ control, also HTTP authentication is needed. My code removes the ClientCredentials behaivor (so yeah, I guess setting them does not make any sense, but I still left those lines in hope that maybe the HTTP transport would try to use them):

HttpTransportBindingElement httpBindingElement = new HttpTransportBindingElement();
httpBindingElement.AuthenticationScheme = AuthenticationSchemes.Basic;
...
PTZClient ptzClient = new PTZClient(customBinding, endPointAddress);
ptzClient.Endpoint.Behaviors.Remove(typeof(System.ServiceModel.Description.ClientCredentials));
UsernameClientCredentials onvifCredentials = new UsernameClientCredentials(new UsernameInfo(_username, _password));
ptzClient.Endpoint.Behaviors.Add(onvifCredentials);
ptzClient.ClientCredentials.UserName.UserName = _username;
ptzClient.ClientCredentials.UserName.Password = _password;

Still when I look at wireshark, i see that the SOAP authentication is generated but no HTTP authentication header is set (well, I already expected that since i have a custom behaivor here). So the question is, if I am creating the binding this way, what are my best options to add HTTP authentication headers? Can I just add a message inspector, and if so, any examples? Must I create a different transport binding? I've seen people advising others to use BasicHttpBinding and then setting the Security property on that, but where do the credentials go in that case and how do I apply the BasicHttpBinding instance to my binding? Are there any callbacks in the WCF that get triggered by the HTTP 401 code that i can hook up to and then provide the header? This is actually my first experience with WCF and so far I've done everything from examples found in the internet, but as for this particular issue I haven't been able to find anything.

Foi útil?

Solução

If anyone is interested this is how I got it working. I combined the BasicHttpBinding with the client credentials in a following way:

TransportSecurityBindingElement transportSecurity = new TransportSecurityBindingElement();
// UsernameCredentials is a class implementing WS-UsernameToken authentication
transportSecurity.EndpointSupportingTokenParameters.SignedEncrypted.Add(new UsernameTokenParameters());
transportSecurity.AllowInsecureTransport = true;
transportSecurity.IncludeTimestamp = false;
TextMessageEncodingBindingElement messageEncoding = new TextMessageEncodingBindingElement(MessageVersion.Soap12, Encoding.UTF8);
HttpClientCredentialType[] credentialTypes = new HttpClientCredentialType[3] { HttpClientCredentialType.None, HttpClientCredentialType.Basic, HttpClientCredentialType.Digest };
...
foreach (HttpClientCredentialType credentialType in credentialTypes)
{
    BasicHttpBinding httpBinding = new BasicHttpBinding(BasicHttpSecurityMode.TransportCredentialOnly);
    httpBinding.Security.Transport.ClientCredentialType = credentialType;
    BindingElementCollection elements = new BindingElementCollection(new BindingElement[1]{messageEncoding});
    foreach(BindingElement element in httpBinding.CreateBindingElements())
    {
        if (element is TextMessageEncodingBindingElement)
            continue;
        elements.Add(element);
    }
    CustomBinding customBinding = new CustomBinding(elements);
    DeviceClient deviceClient = new DeviceClient(customBinding, endPointAddress);
    if (credentialType == HttpClientCredentialType.Basic)
    {
         // Set all credentials, not sure from which one WCF actually takes the value
         deviceClient.ClientCredentials.UserName.UserName = pair[0];
         deviceClient.ClientCredentials.UserName.Password = pair[1];
    }
    else if (credentialType == HttpClientCredentialType.Digest)
    {
        deviceClient.ClientCredentials.HttpDigest.AllowedImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Delegation;
        deviceClient.ClientCredentials.HttpDigest.ClientCredential.UserName = pair[0];
        deviceClient.ClientCredentials.HttpDigest.ClientCredential.Password = pair[1];
    }
}

This works efficiently with a device for which we do not know the authentication mode and works on both (HTTP/SOAP) authentication level.

Outras dicas

I detailed how HTTP digest works in another answer.

Remember that only functions of class PRE_AUTH, according to §5.12.1 of the Core spec, require authentication.

You should invoke a function of any class but PRE_AUTH without any form authentication. If you get a HTTP 401 then you have to use HTTP digset, otherwise you'll have to got with WS-UsernameToken.

You can't directly use HTTP digest because you'll need at least the device to send you the challange for HTTP digest.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top