Как предотвратить дублирование HTTP-запросов с помощью проверки подлинности Windows

Я работаю над клиентским/серверным приложением на основе WCF (WCF является самостоятельным, а не IIS).

У службы WCF есть операция для загрузки части данных на сервер. Контракт примерно выглядит следующим образом:

void UploadChunk(int clientId, byte[] chunk);

Мы используем аутентификацию Windows (Kerberos/NTLM), поэтому мы не можем использовать потоки.

Связывание выглядит так (на стороне клиента и сервера):

new BasicHttpBinding
{   
   Security = new BasicHttpSecurity
   {
      Mode = BasicHttpSecurityMode.TransportCredentialOnly,
      Transport = { ClientCredentialType = HttpClientCredentialType.Windows },
   },
   MaxReceivedMessageSize = 0x7fffffff,
   ReaderQuotas = { MaxArrayLength = 0x800000 },
};

Клиент обращается к службе через прокси-объекты, полученные из System.ServiceModel.ClientBase<TChannel>.

Все это работает отлично, но мы заметили, что клиент WCF отправляет каждый HTTP-запрос дважды, один раз без заголовка auth и еще раз с правильным заголовком auth. Это проблематично, потому что запросы будут довольно большими, и это приводит к тому, что размер запроса будет в два раза больше фактического размера блока.

Я уже выяснил (https://weblog.west-wind.com/posts/2010/Feb/18/NET-WebRequestPreAuthenticate-not-quite-what-it-sounds-like), что параметр WebRequest.PreAuthenticate - true запоминает заголовок auth и повторно использует его для последующих запросов.

Однако из того, что я видел до сих пор, WCF не предоставляет механизм для изменения экземпляра WebRequest.

Есть ли какое-либо решение для этой проблемы?

Ответ 1

Для проверки подлинности Windows всегда будет ответ на вызов (401) для вашего первого запроса.

Если вы контролируете всех клиентов, я считаю, что наиболее практичным решением является реализация операции с минимальной полезной нагрузкой.

Операция void IsAuthenticated() должна выполняться. Для каждого экземпляра прокси-сервера клиента вы вызываете IsAuthenticated до UploadChunk.

Запрос IsAuthenticated предоставит вам ответ на вызов 401 без отправки большой полезной нагрузки, но будет аутентифицировать соединение. Последующие запросы на это соединение не будут оспорены.

Изменить:

Поведение, описанное мной, похоже, применимо только к IIS 8. Поэтому я внимательно рассмотрел два трассировки http.sys: один для хоста IIS и один для самостоятельной службы.

Хостинг-служба IIS, похоже, использует некоторую оптимизацию в отношении аутентификации. Первый запрос на соединение аутентифицируется с помощью Authenticator Sspi Authenticator. Последующие запросы аутентифицируются с помощью Fast Authenticator.

Ни одно из этих событий не присутствует в трассировке самого хоста, что приводит меня к выводу, что самостоятельный хостинг не оптимизирован для проверки подлинности Windows.

http.sys - трассировка IIS

http.sys - трассировать собственный хост

Затем я нашел эту запись в блоге, в которой предлагается решение с использованием NTLM, пользовательской привязки и unsafeConnectionNtlmAuthentication для транспорта HTTP. Если вы хотите использовать NTLM, а проблемы безопасности, выделенные в документации, не являются проблемой это, по-видимому, обеспечивает поведение, которое вы ищете, по трассе http.sys.

http.sys trace - собственный хост с пользовательской привязкой

Для использования сервером привязки

<customBinding>
    <binding name="myBinding">
      <textMessageEncoding messageVersion="Soap11" />
      <httpTransport authenticationScheme="Ntlm" unsafeConnectionNtlmAuthentication="true"/>
    </binding>
  </customBinding>

Для вашего клиента вы можете использовать обычный basicHttpBinding с безопасностью Ntlm:

<basicHttpBinding>
    <binding name="BasicHttpBinding_ITest">
      <security mode="TransportCredentialOnly">
        <transport clientCredentialType="Ntlm" />
      </security>
    </binding>
  </basicHttpBinding>

Ответ 2

Используйте HttWebRequest для вызова службы WCF, создавая полное сообщение SOAP вручную. Это позволит установить PreAuthenticate в true. Сделайте первый вызов без загрузки, Authenticate(). Затем выполните запрос с полезной нагрузкой.