解决浏览器的跨域问题-CORS

为了防止XSS攻击、CSRF攻击等跨域脚本调用问题,浏览器通常具有同源策略,同源意味着:
+ 协议相同
+ 域名相同
+ 端口相同

而同源策略限制了:

  1. 不能向工作在不同源的的服务请求数据(client to server)。
  2. 无法获取不同源的 document/cookie 等 BOM 和 DOM,可以说任何有关另外一个源的
    信息都无法得到 (client to client)。

存在例外情况(scrpit,img等标签)

CORS概述

较早较主流解决跨域问题的方法是JSONP,它利用的就是script-src不会有跨域问题,将回调函数名传给后端,让后端把包裹着响应数据的回调函数(一条js语句)传回来,实现动态回调的效果。

JSONP的限制是,服务端首先要支持,即能接受客户端传来的回调函数名,并且JSONP只能发起GET请求。

CORS是一种规范化的跨域请求解决方案,安全可靠,是目前的主流跨域方案。

只要浏览器检测到响应头带上了 CORS,并且允许的源包括了本网站,那么就不会拦截请求
响应。

CORS 把请求分为两种,一种是简单请求,另一种是需要触发预检请求,这两者是相对的,怎样才算“不简单”?只要属于下面的其中一种就不是简单请求:
1. 使用了除 GET/POST/HEAD 之外的请求方式,如 PUT/DELETE
2. 使用了除 Content-Type/Accept 等几个常用的 http 头这个时候就认为需要先发个预检请求

简单请求

对于简单请求,浏览器直接发出 CORS 请求。具体来说,就是在请求头之中,增加一个 Origin字段。

下面是一个例子,浏览器发现这次跨源 AJAX 请求是简单请求,就自动在头信息之中,添加
一个 Origin 字段。

GET /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

上面的头信息中,Origin 字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求。

如果 Origin 指定的源,不在许可范围内,服务器会返回一个正常的 HTTP 回应。浏览器发现,这个回应的头信息没有包含 Access-Control-Allow-Origin 字段(详见下文),就知道出错了,从而抛出一个错误,被 XMLHttpRequest 的 onerror 回调函数捕获。注意,这种错误无法通过状态码识别,因为 HTTP 回应的状态码有可能是 200。

如果 Origin 指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段。

Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8

上面的头信息之中,有三个与 CORS 请求相关的字段,都以 Access-Control-开头。

  1. Access-Control-Allow-Origin
    该字段是必须的。它的值要么是请求时 Origin 字段的值,要么是一个*,表示接受任意域名的请求。
  2. Access-Control-Allow-Credentials
    该字段可选。它的值是一个布尔值,表示是否允许发送 Cookie。默认情况下,Cookie 不包括在 CORS 请求之中。设为 true,即表示服务器明确许可,Cookie 可以包含在请求中,一起发给服务器。这个值也只能设为 true,如果服务器不要浏览器发送 Cookie,删除该字段即可。

  3. Access-Control-Expose-Headers
    该字段可选。CORS 请求时,XMLHttpRequest 对象的 getResponseHeader()方法只能拿到 6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在 Access-Control-Expose-Headers 里面指定。上面的例子指定,getResponseHeader('FooBar') 可以返回 FooBar 字段的值。

非简单请求

简单请求是那种对服务器有特殊要求的请求,比如请求方法是 PUT 或 DELETE,或者Content-Type 字段的类型是 application/json。

非简单请求的 CORS 请求,会在正式通信之前,增加一次 HTTP 查询请求,称为"预检"请求(preflight)。

浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些 HTTP 动词和头信息字段。只有得到肯定答复,浏览器才会发出正式 XMLHttpRequest 请求,否则就报错。

"预检"请求用的请求方法是 OPTIONS,表示这个请求是用来询问的。头信息里面,关键字段是 Origin,表示请求来自哪个源。

除了 Origin 字段,"预检"请求的头信息包括两个特殊字段。

  1. Access-Control-Request-Method

该字段是必须的,用来列出浏览器的 CORS 请求会用到哪些 HTTP 方法

  1. Access-Control-Request-Headers

该字段是一个逗号分隔的字符串,指定浏览器 CORS 请求会额外发送的头信息字段

服务器收到"预检"请求以后,检查了Origin 、Access-Control-Request-Method 和Access-Control-Request-Headers 字段以后,确认允许跨源请求,就可以做出回应。如果浏览器否定了"预检"请求,会返回一个正常的 HTTP 回应,但是没有任何 CORS 相关的头信息字段。这时,浏览器就会认定,服务器不同意预检请求,因此触发一个错误,被XMLHttpRequest 对象的 onerror 回调函数捕获。

一旦服务器通过了"预检"请求,以后每次浏览器正常的 CORS 请求,就都跟简单请求一样,会有一个 Origin 头信息字段。服务器的回应,也都会有一个 Access-Control-Allow-Origin 头信息字段。

原创文章,作者:彭晨涛,如若转载,请注明出处:https://www.codetool.top/article/%e8%a7%a3%e5%86%b3%e6%b5%8f%e8%a7%88%e5%99%a8%e7%9a%84%e8%b7%a8%e5%9f%9f%e9%97%ae%e9%a2%98-cors/