Запрос Onvif SOAP с аутентификацией уровня SOAP и аутентификацией HTTP

Этот вопрос обсуждался в нескольких темах здесь, но я не смог найти ответ для себя.

Я пытаюсь использовать IP-камеру через интерфейс Onvif. Я создал веб-сервисы из файлов WSDL, доступных на домашней странице Onvif, и добавил собственный код аутентификации SOAP, как было предложено здесь, и я могу получить информацию о возможностях устройства и т. д. и т. д.

Но для некоторых сервисов, например, управления PTZ, также требуется HTTP-аутентификация. Мой код удаляет поведение ClientCredentials (поэтому да, думаю, их установка не имеет никакого смысла, но я все же оставил эти строки в надежде, что, возможно, HTTP-транспорт попытается их использовать):

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;

Тем не менее, когда я смотрю на wireshark, я вижу, что аутентификация SOAP сгенерирована, но заголовок аутентификации HTTP не установлен (ну, я уже ожидал этого, поскольку здесь у меня есть собственное поведение). Итак, вопрос в том, если я создаю привязку таким образом, каковы мои лучшие варианты добавления заголовков аутентификации HTTP? Могу ли я просто добавить инспектор сообщений, и если да, то какие примеры? Должен ли я создавать другую транспортную привязку? Я видел, как люди советовали другим использовать BasicHttpBinding, а затем устанавливали для него свойство Security, но куда в этом случае идут учетные данные и как мне применить экземпляр BasicHttpBinding к моей привязке? Существуют ли какие-либо обратные вызовы в WCF, которые запускаются кодом HTTP 401, к которому я могу подключиться, а затем предоставить заголовок? На самом деле это мой первый опыт работы с WCF и до сих пор я делал все из примеров, найденных в Интернете, но что касается этой конкретной проблемы, я ничего не смог найти.


person Rudolfs Bundulis    schedule 16.01.2014    source источник


Ответы (2)


Если кому интересно, вот как я заработал. Я объединил BasicHttpBinding с учетными данными клиента следующим образом:

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

Это эффективно работает с устройством, для которого мы не знаем режим аутентификации, и работает на обоих уровнях аутентификации (HTTP/SOAP).

person Rudolfs Bundulis    schedule 28.02.2014

Я подробно описал, как работает дайджест HTTP, в другом ответе.

Помните, что только функции класса PRE_AUTH согласно §5.12.1 Основная спецификация, требуется аутентификация.

Вы должны вызывать функцию любого класса, кроме PRE_AUTH, без какой-либо аутентификации формы. Если вы получаете HTTP 401, вам нужно использовать набор копий HTTP, в противном случае вам придется использовать WS-UsernameToken.

Вы не можете напрямую использовать дайджест HTTP, потому что вам потребуется как минимум устройство, чтобы отправить вам вызов для дайджеста HTTP.

person Ottavio Campana    schedule 28.02.2014
comment
Я согласен, но проблема в том, что с приведенным примером кода, даже когда заголовок HTTP-аутентификации возвращается с сервера, соединение не повторяется с ответом, а просто терпит неудачу. - person Rudolfs Bundulis; 28.02.2014