|

CORS 跨域那些事

  • 同源策略

什么是 CORS?

Cross-Origin Resource Sharing (CORS) is an HTTP-header based mechanism that allows a server to indicate any origins (domain, scheme, or port) other than its own from which a browser should permit loading resources. CORS also relies on a mechanism by which browsers make a "preflight" request to the server hosting the cross-origin resource, in order to check that the server will permit the actual request. In that preflight, the browser sends headers that indicate the HTTP method and headers that will be used in the actual request.

CORS 是一种浏览器机制,在发送 AJAX 请求时,如果发现跨域(Origin 不同,Scheme + Domain + Port 定义一个唯一的 Origin),就会自动触发这种机制。

此时,根据请求的方法和 HEADER 不同,浏览器将请求分为两类:

  1. 简单请求(Simple requests)
  2. 非简单请求(not-so-simple request)

简单请求

所谓简单请求有三条规则:

  1. 方法只能是:GET、POST、HEAD
  2. 只能使用这些 HEADER:Accept、Accept-Language、Content-Language、Content-Type、Range(只能使用简单的 bytes=127-255 这种)以及浏览器自动添加的:Connection、User-Agent
  3. 其次 Content-Type 只能是:
    1. application/x-www-form-urlencoded(为了兼容表单,因为历史上表单一直可以发出跨域请求)
    2. multipart/form-data
    3. text/plain

除此之外都是复杂请求,显然,我们常用的 applicaiton/json 不是简单请求。

简单请求处理

对于简单请求,浏览器会在请求时新增一个 Origin: http://api.bob.com 的 HEADER,如果 Server 没有响应 Access-Control-Allow-Origin 就会报错,可以被 XMLHttpRequest 的 onerror 捕获。

非简单请求处理

浏览器会先发一个 Preflight 请求到 Server,通过 Server 的返回来判断是否可以发出正式请求。

什么是 Preflight 请求?

预先检测 Server 是否支持该请求,在 Preflight 请求中会发送在真正请求中使用的方法和所有的 headers。

本质就是一个 HTTP OPTIONS 方法请求,使用三个 HEADER,检测 Server 能不能使用这些属性:

OPTIONS /resource/foo
Access-Control-Request-Method: DELETE
Access-Control-Request-Headers: x-requested-with
Origin: https://foo.bar.org

Preflight 响应

响应 Server 对跨域的支持情况。

  • Access-Control-Allow-Origin:返回允许请求的源
  • Access-Control-Allow-Headers:在请求中服务器支持使用哪些 HEADER,如果请求包含 Access-Control-Request-Headers 字段,则该字段是必须响应的
  • Access-Control-Allow-Methods:在请求中允许使用哪些方法
  • Access-Control-Max-Age:同样方法的缓存时间
  • Access-Control-Allow-Credentials:是否允许请求携带 credentials(Cookies),只有一个值 true(需要在 fetch 配置开启),不允许就不返回该 HEADER
    • 只有 Server 同意也不行,浏览器需要手动设置:xhr.withCredentials = true
    • 注意:如果要发送 Cookie,Access-Control-Allow-Origin 就不能设为星号,必须指定明确的、与请求网页一致的域名。同时,Cookie 依然遵循同源政策,只有用服务器域名设置的 Cookie 才会上传,其他域名的 Cookie 并不会上传,且(跨源)原网页代码中的 document.cookie 也无法读取服务器域名下的 Cookie
      • 感觉很鸡肋,Cookie 要设置到 api.example.com 下,不如使用 JWT HEADER
  • Access-Control-Expose-Headers:控制 JavaScript 能访问到的 Header,例如 fetch 的 Response.headers;XMLHttpRequest 的 getResponseHeader() 方法
HTTP/1.1 204 No Content
Connection: keep-alive
Access-Control-Allow-Origin: https://foo.bar.org
Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE
Access-Control-Allow-Headers: X-Requested-With
Access-Control-Max-Age: 86400

对比 JSONP

CORS 与 JSONP 解决的问题相同,JSONP 只支持 GET 请求,但兼容性更好。

本质

为了安全的原因,浏览器限制脚本中的跨域请求。除非 Server 返回对应的 headers 明确表示允许请求。

WHY?

同源策略规避了不同源之间的相互访问,提高安全性。而 CORS 就是打开的一道口子,在自己明确同意的情况下访问非同源资源,因为在业务上难免有非同源的访问,尤其是前后端分离时代。

另外,我感觉这种策略保护的是 Server,如果 Server 不允许就不能发起请求获取资源(当然,可以挂一层反向代理规避,服务端才没什么同源策略限制,什么都可以伪造)。

打开跨域浏览器

open -n /Applications/Google\ Chrome.app/ --args --disable-web-security --user-data-dir=/Users/用户名/MyChromeDevUserData

资料