Сценарий
У нас есть веб-приложение, которое выполняет вызов веб-службы (размещенной в IIS на той же машине), используя следующий код: -
using (HttpContext.Current.Request.LogonUserIdentity.Impersonate())
{
var client = new Services.ReportFormatter(endpointName); // endpoint name is configured in config
// Services.ReportFormatter is the generated client code using svcutil
var response = client.DoWork();
}
Целью кода является то, что вызов WCF выполняется с использованием учетных данных пользователя веб-приложения (я надеюсь, что это очевидно!), И код работает как прелесть на машинах Dev и QA.Излишне говорить, что он не работает в производственном процессе!
Проблема
Создается исключение .Net:
The HTTP request is unauthorized with client authentication scheme 'Negotiate'. The authentication header received from the server was 'Negotiate,NTLM,Basic realm="Services"'. InnerException:The remote server returned an error: (401) Unauthorized.
Интересно, что в журналах IIS предполагается, что учетные данные пользователя не передаются в запросе WCF
Fields:
cs-method cs-uri-stem cs-uri-query s-port cs-username c-ip sc-status sc-substatus sc-win32-status
GET /MyWebApp/MyPage.aspx - 443 chrisf x.x.x.x
POST /MyServicesApp/ReportFormatter.svc/ntlm - 80 - x.x.x.x 401 2 2148074254
POST /MyServicesApp/ReportFormatter.svc/ntlm - 80 - x.x.x.x 401 1 0
POST /MyServicesApp/ReportFormatter.svc/ntlm - 80 - x.x.x.x 401 1 2148074252
Обратите внимание, что в запросе к веб-приложению зарегистрировано мое имя пользователя, а в запросе к службе - нет.Сравнивая эти журналы с записями с моей машины dev, я вижу свое имя в обоих запросах - поэтому я думаю, что это является причиной сбоя.
Вот конфигурация WCF - хотя тот факт, что она работает в Dev +QA заставляет меня подозревать, что это не вина.Буду признателен за любые идеи / помощь.
Конфигурация веб-приложения (т.е. клиента WCF).
<bindings>
<basicHttpBinding>
<binding name="ReportFormatter" closeTimeout="00:01:00" openTimeout="00:01:00"
receiveTimeout="00:10:00" sendTimeout="00:05:00" allowCookies="false"
bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
maxBufferSize="6553600" maxBufferPoolSize="524288" maxReceivedMessageSize="6553600"
messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered"
useDefaultWebProxy="true" >
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384" maxBytesPerRead="4096" maxNameTableCharCount="16384" />
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Windows" />
<message clientCredentialType="UserName" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint binding="basicHttpBinding" bindingConfiguration="ReportFormatter" contract="Services.ReportFormatter"
name="ReportFormatter_Endpoint" address="http://localhost/MyServicesApp/ReportFormatter.svc/ntlm"/>
</client>
Файл конфигурации приложения веб-службы содержитэто
<bindings>
<basicHttpBinding>
<binding name="BasicHttpWindowsBindingWinCred" maxReceivedMessageSize="20000000">
<readerQuotas maxStringContentLength="20000000" maxArrayLength="1000000" maxBytesPerRead="20000000" />
<security mode="TransportCredentialOnly">
<!-- clientCredentialType Windows requires IIS to be configured to support Windows authentication-->
<transport clientCredentialType="Windows" />
<message clientCredentialType="UserName" />
</security>
</binding>
</basicHttpBinding>
</bindings>## Heading ##
<services>
<service behaviorConfiguration="IIS.Service1Behavior" name="Services.FormatReportImpl">
<endpoint address="ntlm" binding="basicHttpBinding" bindingConfiguration="BasicHttpWindowsBindingWinCred" name="FormatReportBindingWindowsAuthentication" contract="Services.ReportFormatter" bindingNamespace="http://www.donkey.com/Reporting" />
</service>
</services>
Сведения об ОС
Производство: Win 2003 32bit SP2 (IIS 6.0)
QA: Win 2003 32bit SP2 (IIS 6.0)
Dev: Разное!Моя собственная система - Win7 64 (IIS 7.5) - у нас также есть разработчики на Vista.
Причина проблемы и решение
Как обычно, дьявол кроется в деталях.О, это всегда в деталях!Gory детали включены в случае, если они помогают кому-то еще с аналогичными проблемами.
Причина
Чтобы сделать вопрос как можно более простым, я не упомянул, что веб-приложение и Интернетслужба не была размещена на веб-сайте IIS по умолчанию.Вместо этого они размещаются на отдельном веб-сайте (причина в том, что мы размещаем несколько приложений. + Версии одного и того же приложения. На одном сервере), и мы используем информацию заголовка узла для идентификации веб-сайта.
Таким образом, рабочий сайт (и веб-сервис) доступен только через URL-адрес, такой как «app.companyName.com».Таким образом, Я солгал , когда я сказал, что у клиента web.config было address="http://localhost/MyServicesApp/ReportFormatter.svc/ntlm"
На самом деле у него было
address="http://app.companyName.com/MyServicesApp/ReportFormatter.svc/ntlm"
Я чувствую себя немноготеперь это глупо, потому что я знал, что мы не можем передать маркер аутентификации NTLM через границу компьютера - и что касается клиента WCF, app.companyName.com - это удаленный компьютер;он не имеет ни малейшего представления, что DNS-карты, которые обращаются к одной и той же машине, - ну, это может не иметь идеи (продолжайте читать)
Извините
Чтобы извинить мою глупость, здесь есть оправдание: причина, по которой я солгал в этом вопросе, заключалась в том, что я уже проверил, что вышеуказанный тип установки работает на сервере QA,Я проверил, что следующее работает в конфигурационном адресе WCF клиента, работающем на машине QA.
address="http://qaServerName.domainName.local/MyServicesApp/ReportFormatter.svc/ntlm"
Я логически подумал, что этот адрес должен идти в DNS домена, который должен быть разрешени поэтому логически будет проходить ту же обработку, что и «app.companyName.com».Я даже настроил DNS псевдоним «dnsAliasForTheQAServer» (в нашем локальном домене) и затем проверил, что следующее работает
address="http://dnsAliasForTheQAServer.domainName.local/MyServicesApp/ReportFormatter.svc/ntlm"
, и это сработало!Теперь вы должны признать, что если машина думает, что она называется «qaServerName», то вы думаете, что «dnsAliasForTheQAServer.domainName.local» будет обрабатываться как удаленный адрес.Поэтому я решил, что это не может быть проблемой, вызывающей сбой в производственном сервере.
Только когда отчаяние привело нас к созданию новой записи DNS («qaServerNameDnsRecord2»), нам удалось воспроизвести ошибкуна машине QA!Таким образом, я заключаю, что на клиенте WCF и / или DNS выполняется некоторое очень умное разрешение имен, которое разрешает следующие имена хостов
- "qaServerName"
- "qaServerName.domainName.local"
- "dnsAliasForTheQAServer.domainName.local"
to qaServerName - и, следовательно, логически «Localhost» (так как и клиент, и сервер находятся на одной машине)
но разрешение имени не такое умное, когда имя хоста
- "qaServerNameDnsRecord2"
- "qaServerNameDnsRecord2.domainName.local"
Я уверен, что если бы я знал кое-что о DNS или о том, как Windows разрешает имена, то это не было бы сюрпризом. Но я узнал, что в будущем я не буду так быстро, чтобы
Решение
Вооружившись знанием о том, что проблема заключалась в том, что адрес преобразовывался в адрес, который выглядел как удаленный, решение было простым - просто сделать адрес локальным (если веб-служба размещалась на веб-сайте по умолчанию, который было бы очень просто - просто введите address="http://localhost/..."
в качестве адреса конечной точки!).
Первым делом я попытался отредактировать файл hosts на сервере QA и добавить запись, которая сопоставила app.companyName.com с 127.0.0.1, но это не сработало.
Итак, следующее (довольно не элегантное) действие, которое я сделал, - это добавить дополнительную идентификацию для веб-сайта со значением заголовка хоста 127.0.0.1
, а затем изменить конфигурацию на address="http://127.0.0.1/..."
(полагаю, я мог бы использовать localhost
вместо 127.0.0.1). И это все разобралось.