大家好,我是前端西瓜哥。

因为同源策略(Cross-Origin Policy)的存在,浏览器在一个域名下发送另一个域名的 Ajax 请求时,返回的数据通常会被浏览器拦截,让开发者无法拿到返回结果。

图片

这里说 “通常”,是因为浏览器额外提供了一种可以正常使用 Ajax 请求非同源域名接口的机制,也就是我们接下来要说的 跨源资源共享(CORS, Cross-Origin Resource Sharing)。

CORS 会在上图中的第三步中发挥作用。

简单来说,就是请求 b.com 的 HTTP 响应头字段中的一些头字段符合一定规则,返回数据就不会被浏览器拦截,请求者能正常获得返回数据。

简单请求和非简单请求是浏览器发出跨域请求的 两种不同的请求方式,我们来了解一下。

简单请求

发送的请求符合下面的所有情况,就属于简单请求。

  • 请求方法为其中一种:GETPOSTHEAD

  • 除了浏览器自动设置的字段(比如 Connection),仅能人为设置请求头字段 AcceptAccept-LanguageContent-LanguageContent-Type 这个集合内的值。

  • 请求头字段 Content-Type 为其中一种:text/plainmultipart/form-dataapplication/x-www-form-urlencoded

(还有一些更多的琐碎的细节,比如脚本设置、特定浏览器相关的,这里不展开,具体可以查阅官方文档)

可能你会对这个规则的设计很感兴趣,其实它是为了 向后兼容,兼容一些之前没有 Ajax 时就可以发送的跨域请求,比如 form 元素能够产生的请求。

点击表单下的提交按钮,页面跳转然后发送请求。这种写法非常古老了,因为会自动跳转页面且不能拿到返回数据执行下一步的操作,基本开

<form action="https://b.com/api/v1/book/get" method="get">
  <input type="text" name="name" value="clean code" />
  <input type="submit" value="提交"/>
</form>

CORS 机制下,HTTP 响应头字段需要使用头字段 Access-Control-Allow-Origin,将它的值设置为:

  • *:任意域名都允许跨域请求。

  • <origin>:具体的域名,这里不能提供多个域名,只能提供一个域名。这也是可以理解的,通过强制要求防止黑客得到服务端设置的白名单域名。我们不应该透露太多信息。服务端要实现跨域白名单功能,就需要根据请求头字段中

另外,如果请求中携带了身份信息,也就是 Cookie,不能使用 *,而需要指定具体域名。

这样设置后,我们就能正常地拿到其他域名接口返回的数据了。

图片

下面我们再看看非简单请求。

非简单请求

不符合简单请求规定条件的请求,就是 非简单请求

对于简单请求,为了兼容,浏览器会直接将请求发出去。但非简单请求没有兼容的需要,所以浏览器给它加上了严格的复杂机制。

在发出真正的请求前,浏览器会先发一个 OPTION 请求来探探路,这个请求称为 预检请求(preflight)。

图片

浏览器发送的请求,会额外带下以下请求头字段:

  • Access-Control-Request-Method:该字段告知服务端接下来要发起真正跨域请求使用的 HTTP 方法。

  • Access-Control-Request-Headers:该字段告知服务端,当前请求使用的不符合简单请求规则的头字段,比如使用 JSON 结构来作为数据的格式,预检请求就会带上上 Access-Control-Request-Headers: content-type

下面是服务端的回合。

服务端拿到了足够多的请求方信息,然后就可以考虑是否允许跨域了。

服务端的响应头字段可以携带上以下字段:

  • Access-Control-Allow-Origin:用法同上一节的简单请求说明。

  • Access-Control-Allow-Methods:可以发送的请求方法。如 POST, GET, OPTIONS

  • Access-Control-Allow-Headers:可以使用的头字段,如 X-PINGOTHER, Content-Type

  • Access-Control-Max-Age: 86400:缓存的有效时长,单位为秒,设置后,在这段时间内都可以免除预检请求的发送。浏览器自身预设了一个最大缓存时间,防止返回的缓存时间过长(比如好几年)导致的安全问题。

图片

浏览器拿到这些字段后,就会进行对比,如果不符合规则,那么真正的跨域请求将不会发送。如果符合,接下来就会发送真正的跨域请求。

需要注意的是,真正的请求和简单请求一样,需要提供 Access-Control-Allow-Origin 头字段,否则依旧拿不到返回数据。

图片

结尾

CORS 真正的形态是非简单请求,它不会立即发送真正的请求,而是额外发送 OPTION 方法的预检请求,让服务端来决定是否允许即将发送的请求

简单请求是向后兼容的妥协产物,它其实直接向目标发起了真正请求,只是浏览器可能会将返回的数据拦截掉,如果响应头没有设置正确的 Access-Control-Allow-Origin 的话。

我是前端西瓜哥,欢迎关注我。

前端西瓜哥

坚持日更原创,分享前端知识。

104篇原创内容

公众号

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐