CORS 跨域中的 preflight 请求

跨域 CORS preflight AJAX HTTP XHR

我们知道借助Access-Control-Allow-Origin响应头字段可以允许跨域 AJAX, 对于非简单请求,CORS 机制跨域会首先进行 preflight(一个 OPTIONS 请求), 该请求成功后才会发送真正的请求。 这一设计旨在确保服务器对 CORS 标准知情,以保护不支持 CORS 的旧服务器。

Flowchart_showing_Simple_and_Preflight_XHR

Wikipedia: https://upload.wikimedia.org/wikipedia/commons/c/ca/Flowchart_showing_Simple_and_Preflight_XHR.svg

简单请求

简单请求具体是指请求方法是简单方法且请求头是简单头的 HTTP 请求。具体地,

  • 简单方法包括GET, HEAD, POST
  • 简单头包括:Accept, Accept-Language, Content-Language,以及值为application/x-www-form-urlencoded, multipart/form-data, text/plain 其中之一的 Content-Type 头。

对于非简单请求浏览器会首先发送 OPTIONS 请求(成为 preflight), 例如添加一个自定义头部x-foo的 HTTP 请求:

var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.setRequestHeader('x-foo', 'bar');
xhr.send();

服务器需要成功响应(2xx)并在Access-Control-Alow-Headers中包含x-foo (因为它不是简单头部):

OPTIONS /origin-redirect-with-preflight 200
Access-Control-Allow-Headers:x-foo
Access-Control-Allow-Origin:http://index.com:4001
Connection:keep-alive
Content-Length:0

Access-Control-Request-Headers

Access-Control-Request-Headers 是 preflight 请求中用来标识真正请求将会包含哪些头部字段, preflight 请求本身不会发送这些头字段。 例如上述请求中Access-Control-Request-Headers字段的值应该是x-foo。 服务器应当在对应的Access-Control-Allow-Headers响应头中包含这些字段。 否则即使返回 200 preflight 也会失败:

XMLHttpRequest cannot load http://mid.com:4001/access-control-allow-origin-wildcard.
Request header field x-foo is not allowed by Access-Control-Allow-Headers in preflight response.

关于 DNT 请求头

有些浏览器(如 Safari 隐身模式)会在请求中添加DNT头, 但浏览器不会(也不应)因此而发起 preflight。 因为这一请求头是浏览器添加的,也应当对此知情。 所以响应头中也不需要包含Access-Control-Allow-Headers, 参照 W3C Recommendation,满足以下条件即可跳过 preflight:

For request method there either is a method cache match or it is a simple method and the force preflight flag is unset. For every header of author request headers there either is a header cache match for the field name or it is a simple header.

注意只要所有Author Header是简单头即可跳过,这里的DNT虽然不是简单头但它属于 User-Agent Header。 注意在 CORS 被重定向之后这一状况会变得复杂,在重定向 CORS 跨域请求一文有较详细的讨论。

转载请注明来源: http://harttle.com/2016/12/30/cors-preflight.html

看看这个?