仅在一页上从 AJAX POST 获取 NTLM 挑战

Posted

技术标签:

【中文标题】仅在一页上从 AJAX POST 获取 NTLM 挑战【英文标题】:Getting an NTLM Challenge from an AJAX POST on just one page 【发布时间】:2015-07-07 20:12:27 【问题描述】:

这里很神秘。我有一个使用 Windows 身份验证的 ASP.NET MVC 4 Web 应用程序,该应用程序已维护超过 18 个月而没有问题。最近,它被部署到一个新站点,我遇到了以下非常奇怪的行为。

我正在使用 jQuery 1.8.2 $.ajax 调用将数据发布到服务器端点以更新数据。这工作得很好,除了在一个页面上,AJAX POST 触发新的 NTLM 协商。同样的问题也出现在 Chrome、IE 和 Firefox 中。虽然问题在所有浏览器中都是相同的,但其表现方式略有不同:

Firefox:收到来自服务器的 401 Challenge 响应,并在无限循环中弹出用户名/密码对话框,要求提供凭据。取消凭据检查会导致请求失败并返回未经授权的响应。 IE:服务器没有响应,请求状态在网络监视器中显示为“(中止)” Chrome:服务器没有响应,请求状态在网络监视器中显示“(失败)”。

核心问题似乎是 Connection: keep-alive 标头没有与有问题的 AJAX 请求一起发送,但在其他情况下。但是,底层 javascript 代码几乎相同,并且 AJAX 调用在也设置为使用 Windows 身份验证的开发环境中正常运行。

另外,尝试在beforeSend 回调中设置Connection 请求标头无效。

非常感谢您对问题根源的任何见解,或隔离两个 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

编辑

使用来自控制台的不同请求会得到以下结果表(在 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 操作方法的子集,我最初认为这是服务器端问题,但在发现缺少@987654334 的问题后@ header,它实际上似乎是一个客户端问题。问题的具体触发方式对我来说仍然是个谜。

我还验证了工作页面和问题页面的响应标头是否相同。最相关的是,Persistent-Auth: true 标头在两种情况下都会返回。

【问题讨论】:

您是否尝试过从ajax 调用实现error 处理程序?你可能会在这里得到一些额外的信息? @christiandev 是的。 Chrome 返回 net::ERR_INVALID_HANDLE 错误代码,IE 的错误代码为 12019,记录为 ERROR_INTERNET_INCORRECT_HANDLE_STATE。 Firefox 还会报告“无效句柄”。 另外一件事,我添加了与我的所有 [POST] 路由匹配的 [GET] 路由,以防万一这是关于 IE 和 IWA 已知的 GET-before-POST 问题的奇怪表现.这没有效果。 我在失败的帖子中看不到任何内容长度。您是否发布任何数据? @Mike 是的,我刚刚验证了 POST 正文中有表单编码数据。标头是 Chrome 开发工具显示的内容,并被标记为临时标头。我捕获了 Fiddler 的请求,它的 Content-Length 为 106,与正文内容匹配。 【参考方案1】:

导致 NTLM 挑战的操作方法发生了什么?您确定这些特定的操作方法没有通过需要身份验证的代理访问另一台服务器或互联网吗?在您的开发环境中,您的用户或在 IIS 上运行应用程序池的用户可能拥有必要的权限,但服务器上的用户可能没有。

ASP.NET 不会抛出明确的与安全相关的异常,而是将其转换为返回给客户端的 NTLM 质询,并在凭据无效的情况下返回 401,而不是包含引导您的堆栈跟踪的 500服务器帐户没有足够的权限...

【讨论】:

【参考方案2】:

一些疯狂的猜测:

当您请求的角色不在当前登录用户的声明中时,会发生这种情况。验证如果您使用的是[Authorize(Roles = "xyz")],则当前用户确实具有该角色。

尚不清楚您的应用程序是否使用 cookie 进行身份验证。如果是,您应该在请求中看到它。您是否为每个请求设置withCredentials: true

【讨论】:

你可能正在做一些事情,在这里。我们有一个利用我们自己的 ActionFilter 属性的安全设置,因此它可能是与用户->角色映射相关的后端数据问题。感谢您的建议。 不走运。用户具有正确的角色,事实上,AuthorizeAttribute 被设置为全局过滤器,因此如果这是问题所在,所有请求都应该失败。此外,使用 Glimpse,AJAX 请求甚至不会到达 .Net 管道;它立即中止。我注意到开发环境和生产环境之间的唯一区别是问题出现在 IIS 7.5 上,而不是在我的本地 IIS 8.5 上。我将尝试在 IIS Express 7.5 下本地运行,看看是否会重现问题。 @Lucas 您应该从转储中的协商标头中编辑 MIME。它可以被解码以获得你可能不想暴露的信息。 也许看看这个答案,特别考虑浏览器配置并确保设置 withCredentials 属性 - ***.com/a/47916916/5196274

以上是关于仅在一页上从 AJAX POST 获取 NTLM 挑战的主要内容,如果未能解决你的问题,请参考以下文章

为什么我的SESSION数组在一页上可以,但在另一页上可以为空?

Mailchimp 在一页上嵌入两个表单

仅在第一页页眉上的图像

在一页上邮寄两个嵌入的表格

PHP Wordpress:仅在类别/存档的第一页上显示

背景未在一页上覆盖网站