Подтвердить что ты не робот

Получение вызова NTLM из AJAX POST на одной странице

Довольно тайна здесь. У меня есть веб-приложение ASP.NET MVC 4 с использованием проверки подлинности Windows, которая поддерживается более 18 месяцев без проблем. Недавно он был развернут на новый сайт, и я столкнулся со следующим, очень странным поведением.

Я использую вызов jQuery 1.8.2 $.ajax для данных POST для конечных точек сервера для обновления данных. Это работает отлично, за исключением одной страницы, где AJAX POST запускает новое соглашение NTLM. Та же проблема проявляется в Chrome, IE и Firefox. Хотя проблема одинакова во всех браузерах, она проявляется несколько по-разному:

  • Firefox: получен ответ 401 Challenge с сервера и открывается диалоговое окно с именем пользователя/пароля с запросом учетных данных в бесконечном цикле. Отмена проверки учетных данных приводит к сбою запроса с помощью неавторизованного ответа.
  • IE: нет ответа от сервера, и статус запроса отображается как "(Отменено)" на мониторе сети.
  • Chrome: от сервера нет ответа, а статус запроса показывает "(отказ)" на мониторе сети.

Основная проблема заключается в том, что заголовок Connection: keep-alive не отправляется с проблемным запросом AJAX, но в других случаях. Однако базовый код JavaScript почти идентичен, и функция AJAX правильно работает в среде разработки, которая также настроена на использование проверки подлинности Windows.

Кроме того, попытка набора заголовка запроса Connection в обратном вызове beforeSend не имеет эффекта.

Любое понимание корня проблемы или способы выделения любой разницы между двумя AJAX POST наиболее ценятся.

Рабочий код и заголовки запроса

$.ajax({
   url: url,
   type: "POST",
   data: $("#myForm").serialize(),
   cache: false,
   success: function (response) {
   }
});


Accept:*/*
Accept-Encoding:gzip, deflate
Accept-Language:en-US,en;q=0.8
Connection:keep-alive
Content-Length:621
Content-Type:application/x-www-form-urlencoded; charset=UTF-8
Host:www.xxx.yyy.zzz
Origin:http://www.xxx.yyy.zzz
Referer:http://www.xxx.yyy.zzz/app/resource/path
User-Agent:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36
X-Requested-With:XMLHttpRequest

Сбой кода и заголовков запросов

$.ajax({
    url: url,
    type: "POST",
    data: data,
    cache: false,
    success: function (data, status, xhr) {
    }
 });

 WARN: Provisional headers are shown
 Accept:*/*
 Content-Type:application/x-www-form-urlencoded; charset=UTF-8
 Origin:http://www.xxx.yyy.zzz
 Referer:http://www.xxx.yyy.zzz/app/resource/item/1
 User-Agent:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36
 X-Requested-With:XMLHttpRequest

Я также просмотрел сетевой процесс в браузере Chrome chrome://net-internals/#events. Вот журнал событий из неудавшегося запроса в точке, в которой он отклоняется от успешного. В случае неудавшегося запроса "HTTP/1.1 401 Unauthorized" успешный запрос получает ответ "HTTP/1.1 200 OK", предположительно из-за присутствия заголовка Connection: keep-alive.

2303: URL_REQUEST
Start Time: 2015-04-28 13:53:41.788

t=14736 [st= 0] +REQUEST_ALIVE  [dt=71]
t=14736 [st= 0]    URL_REQUEST_DELEGATE  [dt=0]
t=14736 [st= 0]   +URL_REQUEST_START_JOB  [dt=70]
                   --> load_flags = 2688000 (BYPASS_DATA_REDUCTION_PROXY | MAYBE_USER_GESTURE | REPORT_RAW_HEADERS | VERIFY_EV_CERT)
               --> method = "POST"
               --> priority = "LOW"
               --> upload_id = "0"
               --> url = "http://..."
t=14736 [st= 0]      URL_REQUEST_DELEGATE  [dt=0]
t=14736 [st= 0]      HTTP_CACHE_GET_BACKEND  [dt=0]
t=14736 [st= 0]      URL_REQUEST_DELEGATE  [dt=0]
t=14736 [st= 0]     +HTTP_STREAM_REQUEST  [dt=0]
t=14736 [st= 0]        HTTP_STREAM_REQUEST_BOUND_TO_JOB
                       --> source_dependency = 2305 (HTTP_STREAM_JOB)
t=14736 [st= 0]     -HTTP_STREAM_REQUEST
t=14736 [st= 0]     +HTTP_TRANSACTION_SEND_REQUEST  [dt=0]
t=14736 [st= 0]        HTTP_TRANSACTION_SEND_REQUEST_HEADERS
                       --> POST ... HTTP/1.1
                       Host: www.xxx.yyy.zzz
                       Connection: keep-alive
                       Content-Length: 105
                       Accept: */*
                       Origin: http://www.xxx.yyy.zzz
                       User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36
                       X-Requested-With: XMLHttpRequest
                       Content-Type: application/x-www-form-urlencoded; charset=UTF-8
                       Referer: http://www.xxx.yyy.zzz/app/resource/item/1
                       Accept-Encoding: gzip, deflate
                       Accept-Language: en-US,en;q=0.8
t=14736 [st= 0]        HTTP_TRANSACTION_SEND_REQUEST_BODY
                       --> did_merge = true
                       --> is_chunked = false
                       --> length = 105
t=14736 [st= 0]     -HTTP_TRANSACTION_SEND_REQUEST
t=14736 [st= 0]     +HTTP_TRANSACTION_READ_HEADERS  [dt=0]
t=14736 [st= 0]        HTTP_STREAM_PARSER_READ_HEADERS  [dt=0]
t=14736 [st= 0]        HTTP_TRANSACTION_READ_RESPONSE_HEADERS
                   --> HTTP/1.1 401 Unauthorized
                       Content-Type: text/html
                       Server: Microsoft-IIS/7.5
                       WWW-Authenticate: Negotiate
                       WWW-Authenticate: NTLM
                       X-Powered-By: ASP.NET
                       X-UA-Compatible: IE=9
                       Date: Tue, 28 Apr 2015 18:53:41 GMT
                       Content-Length: 1293

Edit

Игра с другим запросом с консоли дает следующую таблицу результатов (в Chrome). Текущий базовый URL http://IPAddress /app/topic/item, и весь тест просто выполняет $.ajax({ url: url, type: 'POST' })

+--------------------------------------+----------------------------+
|   URL                                | Response                   |
+--------------------------------------+----------------------------+
| http://IP/app/topic/item/1/subitem/1 | net::ERR_INVALID_HANDLE    |
| //IP/app/topic/item/1/subitem/1      | net::ERR_INVALID_HANDLE    |
| /app/topic/item/1/subitem/1          | net::ERR_INVALID_HANDLE    |
| 1/subitem/1                          | net::ERR_INVALID_HANDLE    |
| 1/foo                                | 404 (Not Found) [expected] |
| 1                                    | 302 (Redirect)  [expected] |
+--------------------------------------+----------------------------+

Поскольку ошибка только влияет на подмножество методов действий POST в одном контроллере, я изначально думал, что это проблема на стороне сервера, но после обнаружения проблемы с отсутствующим Connection, на самом деле это проблема на стороне клиента. Точно как проблема срабатывает, для меня остается загадкой.

Я также убедился, что заголовки ответов для рабочей страницы и проблемной страницы одинаковы. Наиболее важно, заголовок Persistent-Auth: true всегда возвращается в обоих случаях.

4b9b3361

Ответ 1

Некоторые дикие догадки:

  • Это происходит, когда вы запрашиваете Роль, которая не находится в Заявке текущего пользователя. Убедитесь, что, если вы используете [Authorize(Roles = "xyz")], текущий пользователь фактически имеет эту роль.

  • Неясно, использует ли ваше приложение cookie для аутентификации. Если да, вы должны увидеть это с запросом. Вы устанавливаете withCredentials: true для каждого запроса?

Ответ 2

Что происходит в методах действий, вызывающих вызов NTLM? Вы уверены, что эти конкретные методы действий не имеют доступа к другому серверу или к интернету через прокси-сервер, который требует аутентификации? В вашей среде разработки у вашего пользователя или пользователя, использующего пул приложений в IIS, могут быть необходимые разрешения, но пользователь на сервере не может.

Вместо того, чтобы бросать четкое исключение, связанное с безопасностью, ASP.NET переведет это в вызов NTLM, который будет возвращен клиенту, и 401 в случае недопустимых учетных данных, а не 500, содержащий стек, который приведет вас к точке где учетная запись сервера не имела достаточных привилегий...