【问题标题】:Chrome v37/38 CORS failing (again) with 401 for OPTIONS pre-flight requestsChrome v37/38 CORS(再次)失败,401 用于 OPTIONS 飞行前请求
【发布时间】:2014-12-05 11:13:01
【问题描述】:

从 Chrome 版本 37 开始,如果服务器启用了身份验证,即使所有 CORS 标头都已正确设置,预先发送的跨域请求也会失败(再次)。这是在localhost(我的开发PC)上。

你们中的一些人可能知道 Chrome/CORS/auth 错误的历史,尤其是在涉及 HTTPS 时。我的问题涉及HTTPS:我有一个AngularJS应用程序从localhost:8383localhost:8081上的Java(Jetty)服务器交谈,该服务器已激活HTTP BASIC身份验证。 GET 工作正常,但 POST 失败并显示 401:

XMLHttpRequest cannot load http://localhost:8081/cellnostics/rest/patient.
Invalid HTTP status code 401

我之前编写了一个自定义 (Java) CORS 过滤器,用于设置正确的 CORS 标头,直到 v36。它在 v37 和最新的 v38 (38.0.2125.101 m) 中失败。它在 Internet Explorer 11 (11.0.9600) 和 Opera 12.17 (build 1863) 上仍然可以正常工作

GET 请求成功,但 POST 失败。由于内容类型:“application/json”,Chrome 似乎正在预检我的所有 POST,而且预检的 OPTIONS 请求失败了。

在 Angular 应用程序中,我明确设置了以下请求标头。 AFAIK withCredentials 的此设置应确保即使对于 OPTIONS 请求也发送凭据:

//Enable cross domain calls
$httpProvider.defaults.useXDomain = true;

//Send all requests, even OPTIONS, with credentials
$httpProvider.defaults.withCredentials = true;

以下是请求/响应。可以看到Access-Control-Allow-Methods 标头中启用了 OPTIONS 方法。您还可以看到 Javascript 应用的来源已明确启用:Access-Control-Allow-Origin: http://localhost:8383

Remote Address:[::1]:8081
Request URL:http://localhost:8081/cellnostics/rest/medicaltest
Request Method:OPTIONS
Status Code:401 Full authentication is required to access this resource

Request headers:

Accept:*/*
Accept-Encoding:gzip,deflate,sdch
Accept-Language:en-US,en;q=0.8,af;q=0.6
Access-Control-Request-Headers:accept, content-type
Access-Control-Request-Method:POST
Connection:keep-alive
Host:localhost:8081
Origin:http://localhost:8383
Referer:http://localhost:8383/celln-web/index.html
User-Agent:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.101 Safari/537.36

Response headers:

Access-Control-Allow-Credentials:true
Access-Control-Allow-Headers:Cache-Control, Pragma, Origin, Authorization, Content-Type, X-Requested-With, Accept
Access-Control-Allow-Methods:POST, GET, OPTIONS, PUT, DELETE
Access-Control-Allow-Origin:http://localhost:8383
Access-Control-Max-Age:3600
Content-Length:0
Server:Jetty(8.1.8.v20121106)
WWW-Authenticate:Basic realm="Cellnostics"

有人知道我还应该做什么吗?我确保在测试、重新启动之前清除 Chrome 缓存,并确保在重新启动之前没有运行任何后台 Chrome 进程,所以我很确定没有挥之不去的身份验证缓存问题。

我不得不切换到 IE 11 来测试我的 Web 开发。相同的客户端和服务器设置仍然适用于 IE 和 Opera,以及 Chrome/CORS 错误的历史,让我怀疑 Chrome。

编辑:这是 Chrome 网络内部事件列表的摘录:

t=108514 [st=0]   +URL_REQUEST_START_JOB  [dt=4]
    --> load_flags = 336011264 (BYPASS_DATA_REDUCTION_PROXY | DO_NOT_SAVE_COOKIES | DO_NOT_SEND_AUTH_DATA | DO_NOT_SEND_COOKIES | MAYBE_USER_GESTURE | VERIFY_EV_CERT)
    --> method = "OPTIONS"
    --> priority = "LOW"
    --> url = "http://localhost:8081/cellnostics/rest/patient"
...
t=108516 [st=2] HTTP_TRANSACTION_SEND_REQUEST_HEADERS
--> OPTIONS /cellnostics/rest/patient HTTP/1.1
   Host: localhost:8081
   Connection: keep-alive
   Access-Control-Request-Method: POST
   Origin: http://localhost:8383
   User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.101 Safari/537.36
   Access-Control-Request-Headers: accept, content-type
   Accept: */*
   Referer: http://localhost:8383/celln-web/index.html
   Accept-Encoding: gzip,deflate,sdch
   Accept-Language: en-US,en;q=0.8,af;q=0.6

因此,即使我明确设置了withCredentials = true,但授权标头似乎并未与 OPTIONS 预检一起发送。

但是,为什么 IE 和 Opera 仍然可以工作? Chrome 在这方面是否更符合标准?为什么它可以工作,然后从 v37 开始失败?

编辑:Chrome 开发工具没有在上面的转储中显示请求的Content-Type,但这里它来自网络日志。第一张图片显示了禁用服务器身份验证时的 POST,内容类型正确发送为“应用程序/json”。第二张图片是启用身份验证时,显示 OPTIONS 请求失败(似乎 OPTIONS 总是以内容类型“text/plain”发送?)。

【问题讨论】:

  • 您仍未显示所有响应标头。您能否也显示对 OPTIONS 请求的完整响应?如果您在开发者工具中看不到它,请访问 chrome://net-internals/#events,然后发出请求,然后返回该 chrome 页面并找到描述您的请求的 URL_REQUEST。
  • 当我遇到 CORS 问题时,是因为“内容类型”。而且我在您的查询中没有看到任何内容类型。
  • 查看我上面的编辑,显示带有 Content-Type 的网络日志

标签: google-chrome cors


【解决方案1】:

@Cornel Masson,你解决问题了吗?我不明白为什么您的服务器要求您对 OPTIONS 请求进行身份验证,但我在 SAP NetWeaver 服务器上遇到了同样的问题。我已经阅读了整个 CORS 规范(我推荐),所以我可以澄清你的一些疑问。

关于你的句子

在 Angular 应用程序中,我明确设置了以下请求标头。 AFAIK withCredentials 的此设置应确保即使对于 OPTIONS 请求也发送凭据:

  • 根据CORS specification,当用户代理(即浏览器)预检请求(使用 OPTIONS HTTP 方法的请求)时,它必须排除用户凭据(cookie、HTTP 身份验证...),因此 any OPTIONS 请求不能被请求为已验证。浏览器将请求经过身份验证的实际请求(使用请求的 HTTP 方法,如 GET、POST...),而不是预检请求。
  • 因此浏览器不得在 OPTIONS 请求中发送凭据。他们会在实际请求中做。如果你写 withCredentials = true 浏览器应该按照我说的去做。

根据你的说法:

由于内容类型:“application/json”:

  • 规范还说,当标头不是“简单标头”时,浏览器将发出预检请求,这里你有什么意思:

    如果标头字段名称是与 Accept、Accept-Language 或 Content-Language 匹配的 ASCII 大小写不敏感的匹配,或者如果它是与 Content-Type 和标头字段值媒体类型(不包括参数)是不区分大小写的 ASCII 匹配 application/x-www-form-urlencodedmultipart/form-data 或 文本/纯文本。

  • application/json 不包括在内,因此浏览器必须按原样预检请求。
如果有人找到解决方案,将不胜感激。

编辑:我刚刚发现一个人有同样的问题,反映了真正的问题,如果你和他使用相同的服务器,你会很幸运,https://evolpin.wordpress.com/2012/10/12/the-cors/

【讨论】:

  • 我没有找到解决方案,但有一个解决方法。由于问题仅发生在 localhost 上,因此我设置了构建过程以在 localhost(即我的开发 PC)上运行时禁用服务器上的身份验证。对于所有其他部署(开发服务器、测试、生产),启用身份验证。我不喜欢这种解决方法,因为我更喜欢在我开发的内容和部署的内容之间完全平等。为了降低风险,我总是首先部署到已启用身份验证的本地 VM,以便在发布前进行最终冒烟测试。
  • 关于您的解决方法,有一点我不明白。如果您的本地开发服务器作为您的开发应用程序位于 localhost 中,则无需禁用身份验证,以便您的应用程序和服务器位于同一域 (localhost) 中,因此不需要 CORS(它开始发挥作用从与应用不同的域访问资源时)。
  • 它位于本地主机上的不同 端口 上。只要 URL 的任何组成部分不同,即方案(例如 http://)、域(例如 www.example.com)或端口(例如 8081 与 8080)不同,CORS 就会应用。问题在于,对于单页 Javascript 客户端的开发,开发人员的 IDE 或其他开发工具通常允许在其自己的端口上从嵌入式 Web 服务器快速提供应用程序,这与本地 REST 的端口不同服务器(不同的项目/应用程序)正在监听。本地主机上的开发人员非常常见的问题。
  • 我现在明白了,谢谢。我面临同样的问题,但启用了身份验证。一个近似值是使用反向代理,但是代理必须在与应用程序 Web 服务器相同的端口上进行侦听,因此我需要找到一个既可以是事物(Web 服务器又可以是反向代理)的服务器。我听说 Jetty 可以做到这一点。也许你想试一试。祝你好运
【解决方案2】:

这里也一样。我使用 Windows NTLM 身份验证。在 Chrome 版本 37 之前它工作正常。在版本 37、38 上,由于 PUT 和 POST 的飞行前选项中缺少请求授权标头,因此失败并显示 401(未授权)。

服务器端是 Microsoft Web Api 2.1。我尝试了各种 CORS,包括 Microsoft 提供的最新 NuGet 包,但均无济于事。

我必须通过发送 GET 请求而不是 POST 来解决 Chrome 问题,并在多个请求中破坏相当大的数据,因为 URL 自然有大小限制。

这里是请求/响应标头:


Request URL: http://localhost:8082/api/ConfigurationManagerFeed/
Method: OPTIONS
Status: 401 Unauthorized

Request Headers
Accept: */*
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8,ru;q=0.6
Access-Control-Request-Headers: accept, content-type
Access-Control-Request-Method: POST
Connection: keep-alive
Host: localhost:8082
Origin: http://localhost:8383
Referer: http://localhost:8383/Application/index.html
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.104 Safari/537.36
X-DevTools-Emulate-Network-Conditions-Client-Id: 49E7FC68-A65C-4318-9292-852946051F27

Response Headers
Cache-Control: private
Content-Length: 6388
Content-Type: text/html; charset=utf-8
Date: Fri, 24 Oct 2014 13:40:07 GMT
Server: Microsoft-IIS/7.5
WWW-Authenticate: Negotiate
NTLM
X-Powered-By: ASP.NET

【讨论】:

  • 现在,我通过在本地主机上进行测试时禁用 HTTP BASIC 身份验证来解决这个问题。在生产中,webapp 是从与后端相同的主机/端口提供服务的,所以那里没有 CORS 问题。
  • 我工作的Web Api被多个应用程序使用; SharePoint 和一些移动应用程序也在其中,因此在同一个网站上托管并不是一个好的解决方案。有趣的是 Fiddler “修复”了来自 ​​Chrome 的请求,因为 Fiddler 代理了请求。因此,在您的情况下,您可以在开发框上使用 Fiddler。
  • 是的,这是一个非常有效的生产场景和恼人的 Chrome 错误。我没有考虑总是代理一切,这可能是一个想法,谢谢。
【解决方案3】:

在 MS IIS 上,我通过覆盖 Microsoft 标准页面生命周期实现了另一种解决方法,即在 global.ascx 中的 HTTP 请求开始时处理 OPTIONS:

public class Global : HttpApplication
{
    /// <summary>Check and cofigure CORS Pre-flight request on Begin Request lifecycle
    /// </summary>
    protected void Application_BeginRequest()
    {
        if (Request.Headers.AllKeys.Contains(CorsHandler.Origin) && Request.HttpMethod == "OPTIONS")
        {
            Response.StatusCode = (int)HttpStatusCode.OK;
            Response.Headers.Add(CorsHandler.AccessControlAllowCredentials, "true");
            Response.Headers.Add(CorsHandler.AccessControlAllowOrigin, Request.Headers.GetValues(CorsHandler.Origin).First());
            string accessControlRequestMethod = Request.Headers.GetValues(CorsHandler.AccessControlRequestMethod).FirstOrDefault();
            if (accessControlRequestMethod != null)
            {
                Response.Headers.Add(CorsHandler.AccessControlAllowMethods, accessControlRequestMethod);
            }
            var hdrs = Request.Headers.GetValues(CorsHandler.AccessControlRequestHeaders).ToList();
            hdrs.Add("X-Auth-Token");
            string requestedHeaders = string.Join(", ", hdrs.ToArray());
            Response.Headers.Add(CorsHandler.AccessControlAllowHeaders, requestedHeaders);
            Response.Headers.Add("Access-Control-Expose-Headers", "X-Auth-Token");
            Response.Flush();
        }
    }
}

【讨论】:

  • 什么是CorsHandler
【解决方案4】:

我们在尝试调试在 localhost:4200 上运行的 Angular 4 前端应用程序时遇到了同样的问题(使用 Angular CLI Live Development Server)。 Angular 应用程序通过 Windows 身份验证向在 localhost(IIS Web 服务器)上运行的 ASP .Net WebApi2 应用程序发出 http 请求。该问题仅在 Chrome 中发出 POST 请求时发生(即使我们的 WebApi 配置为 COR)。我们能够通过启动 Fiddler 并运行反向代理来临时解决这个问题,直到我们找到这篇文章(谢谢)。

Chrome 调试器中的错误信息: 对预检请求的响应未通过访问控制检查:请求的资源上不存在“Access-Control-Allow-Origin”标头

将以下代码添加到我们的 Web Api2 - Global.asax 文件中解决了这个问题。

如果您正在寻找“CorsHandler”,您可以简单地将 George 的帖子替换为硬编码的字符串值,如下所示:

    protected void Application_BeginRequest()
    {
        if (Request.Headers.AllKeys.Contains("Origin") && Request.HttpMethod == "OPTIONS")
        {
            Response.StatusCode = (int)HttpStatusCode.OK;
            Response.Headers.Add("Access-Control-Allow-Credentials", "true");
            Response.Headers.Add("Access-Control-Allow-Origin", Request.Headers.GetValues("Origin").First());
            string accessControlRequestMethod = Request.Headers.GetValues("Access-Control-Request-Method").FirstOrDefault();
            if (accessControlRequestMethod != null)
            {
                Response.Headers.Add("Access-Control-Allow-Methods", accessControlRequestMethod);
            }
            var hdrs = Request.Headers.GetValues("Access-Control-Request-Headers").ToList();
            hdrs.Add("X-Auth-Token");
            string requestedHeaders = string.Join(", ", hdrs.ToArray());
            Response.Headers.Add("Access-Control-Allow-Headers", requestedHeaders);
            Response.Headers.Add("Access-Control-Expose-Headers", "X-Auth-Token");
            Response.Flush();
        }
    }

亲切的问候,

克里斯

【讨论】:

    猜你喜欢
    • 2016-06-28
    • 1970-01-01
    • 2014-11-04
    • 2014-12-27
    • 2014-02-14
    • 2018-04-13
    • 1970-01-01
    • 1970-01-01
    • 2018-03-20
    相关资源
    最近更新 更多