Chrome v37 / 38 CORS(再次)失败,401为OPTIONS飞行前请求

时间:2014-10-10 10:00:50

标签: google-chrome cors

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

有些人可能知道Chrome / CORS / auth错误的历史,特别是涉及HTTPS时。我的问题涉及HTTPS:我有一个AngularJS应用程序从localhost:8383localhost:8081上的Java(Jetty)服务器通信,该服务器已激活HTTP BASIC auth。 GET工作正常,但POST失败了401:

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

我之前编写了一个自定义(Java)CORS过滤器,它设置了正确的CORS标头,直到第36版。它在v37和最新的v38(38.0.2125.101 m)中失败。使用Internet Explorer 11(11.0.9600)和Opera 12.17(版本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来测试我的网站开发。事实上,相同的客户端和服务器设置仍适用于IE和Opera,以及存在Chrome / CORS错误的历史,这让我怀疑Chrome。

编辑:以下是Chrome net-internals事件列表的摘录:

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运行前发送Authorization标头。

然而,为什么IE和Opera仍然有效? Chrome在这方面是否更符合标准?为什么它会起作用然后从v37开始失败?

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

CORS POST with NO auth on server CORS POST with auth enabled on server

4 个答案:

答案 0 :(得分:7)

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

关于你的句子

  

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

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

根据你的判决:

  

由于内容类型的原因,Chrome看起来像是在推广我的所有POST:" application / json":

  • 规范还说,当标题不是简单的标题时,浏览器会发出预检请求。这就是你的意思:

      

    如果头字段名称是对Accept,Accept-Language或Content-Language的ASCII不区分大小写的匹配,或者如果它是Content-Type的ASCII不区分大小写的匹配,则称头是简单头标题字段值媒体类型(不包括参数)是 application / x-www-form-urlencoded multipart / form-data 或<的ASCII不区分大小写的匹配项强>文本/纯

  •   
  • application / json 不包含在内,因此浏览器必须预先处理请求。
  •   
  如果有人找到解决方案,我们将不胜感激。

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

答案 1 :(得分:2)

同样在这里。我使用Windows NTLM身份验证。直到Chrome版本37,它工作正常。在版本37,38上,由于在PUT和POST上的飞行前OPTIONS中缺少请求授权头,因此401(未授权)失败。

服务器端是Microsoft Web Api 2.1。我尝试了各种CORS,包括微软最新的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

答案 2 :(得分:2)

在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();
        }
    }
}

答案 3 :(得分:2)

在尝试调试在localhost:4200(使用Angular CLI Live Development Server)上运行的Angular 4前端应用程序时,我们遇到了同样的问题。 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();
        }
    }

亲切的问候,

克里斯