浏览器跨域

基础知识

同源政策

概念

1995年,同源政策由 Netscape 公司引入浏览器。目前,所有浏览器都实行这个政策。
最初,它的含义是指,A 网页设置的 Cookie,B 网页不能打开,除非这两个网页“同源”。所谓“同源”指的是“三个相同”:

  • 协议相同
  • 域名相同
  • 端口相同

同源政策的目的,是为了保证用户信息的安全,防止恶意的网站窃取数据。

例如:http://www.example.com/dir/page.html这个网址,协议是http://,域名是www.example.com,端口是80(默认端口可以省略)

目的

设想这样一种情况:

A 网站是一家银行,用户登录以后,A 网站在用户的机器上设置了一个 Cookie,包含了一些隐私信息(比如存款总额)。

用户离开 A 网站以后,又去访问 B 网站,如果没有同源限制,B 网站可以读取 A 网站的 Cookie,那么隐私信息就会泄漏。

更可怕的是,Cookie 往往用来保存用户的登录状态,如果用户没有退出登录,其他网站就可以冒充用户,为所欲为。

因为浏览器同时还规定,提交表单不受同源政策的限制。由此可见,同源政策是必需的,否则 Cookie 可以共享,互联网就毫无安全可言了。

受到的限制

当前,如果非同源,共有三种行为受到限制:

  • Cookie、LocalStorage 和 IndexDB 无法读取
  • DOM 无法获得
  • AJAX 请求不能发送

这里说明一下,无法跨域是浏览器对于用户安全的考虑,如果自己写个没有同源策略的浏览器,完全不用考虑跨域问题了。

跨域的解决方式

  1. CORS
  2. JSONP
  3. 服务器代理
  4. document.domain
  5. postMessage

CORS

CORS 是一个 W3C 标准,全称是跨域资源共享(Cross-origin resource sharing),它允许浏览器向跨源服务器,发出XMLHttpRequest请求。

整个 CORS 通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS 通信与普通的 AJAX 通信没有差别,代码完全一样。浏览器一旦发现 AJAX 请求跨域,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感知。因此,实现 CORS 通信的关键是服务器。只要服务器实现了 CORS 接口,就可以跨域通信。

服务器端配置

CORS常用的配置项有以下几个:

  • Access-Control-Allow-Origin(必含) – 允许的域名,只能填 *(通配符)或者单域名。
  • Access-Control-Allow-Methods(必含) – 这允许跨域请求的 http 方法(常见有 POST、GET、OPTIONS)。
  • Access-Control-Allow-Headers(当预请求中包含 Access-Control-Request-Headers 时必须包含) – 这是对预请求当中 Access-Control-Request-Headers 的回复,和上面一样是以逗号分隔的列表,可以返回所有支持的头部。
  • Access-Control-Allow-Credentials(可选) – 表示是否允许发送Cookie,只有一个可选值:true(必为小写)。如果不包含cookies,请略去该项,而不是填写false。这一项与 XmlHttpRequest 对象当中的 withCredentials 属性应保持一致,即 withCredentials 为true时该项也为true;withCredentials 为false时,省略该项不写。反之则导致请求失败。
  • Access-Control-Max-Age(可选) – 以秒为单位的缓存时间。在有效时间内,浏览器无须为同一请求再次发起预检请求。

CORS 跨域的判定流程

  • 浏览器先根据同源策略对前端页面和后台交互地址做匹配,若同源,则直接发送数据请求;若不同源,则发送跨域请求。
  • 服务器收到浏览器跨域请求后,根据自身配置返回对应文件头。若未配置过任何允许跨域,则文件头里不包含 Access-Control-Allow-origin 字段,若配置过域名,则返回 Access-Control-Allow-origin + 对应配置规则里的域名的方式。
  • 浏览器根据接受到的 响应头里的 Access-Control-Allow-origin 字段做匹配,若无该字段,说明不允许跨域,从而抛出一个错误;若有该字段,则对字段内容和当前域名做比对,如果同源,则说明可以跨域,浏览器接受该响应;若不同源,则说明该域名不可跨域,浏览器不接受该响应,并抛出一个错误。

上面说到的两种类型的报错,控制台输出是不一样的:

  1. 服务器允许跨域请求,但是 Origin 指定的源,不在许可范围内,服务器会返回一个正常的HTTP回应。浏览器发现,这个回应的头信息没有包含 Access-Control-Allow-Origin 字段,就知道出错了,从而抛出一个错误,被 XMLHttpRequest的onerror 回调函数捕获。注意,这种错误无法通过状态码识别,因为 HTTP 回应的状态码有可能是200。
<!--控制台返回结果-->
 XMLHttpRequest cannot load http://localhost/city.json.
 The 'Access-Control-Allow-Origin' header has a value 'http://segmentfault.com' that is not equal to the supplied origin. 
 Origin 'http://www.zhihu.com' is therefore notallowed access.
  1. 服务器不允许任何跨域请求
<!--控制台返回结果-->
XMLHttpRequest cannot load http://localhost/city.json.
No 'Access-Control-Allow-Origin' header is present on the requested resource. 
Origin 'http://www.zhihu.com' is therefore not allowed access.

Cores 使用

实际上浏览器将CORS请求分成两类:

  • 简单请求(simple request)
  • 非简单请求(not-so-simple request)

简单请求(simple request)

简单请求是指满足以下条件的(一般只考虑前面两个条件即可):

  1. 使用 GET、POST、HEAD 其中一种请求方法。
  2. HTTP的头信息不超出以下几种字段:
  • Accept
  • Accept-Language
  • Content-Language
  • Last-Event-ID
  • Content-Type:只限于三个值 application/x-www-form-urlencoded、multipart/form-data、text/plain
  1. 请求中的任意XMLHttpRequestUpload 对象均没有注册任何事件监听器;
  2. XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问。 请求中没有使用 ReadableStream 对象。

简单请求就是普通 HTML Form 也可以发出的请求,比如表单的 method 如果指定为 POST ,可以用 enctype 属性指定用什么方式对表单内容进行编码

对于简单请求,浏览器直接发起 CORS 请求,具体来说就是服务器端会根据请求头信息中的 origin 字段(包括了协议 + 域名 + 端口),来决定是否同意这次请求。

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

Access-Control-Allow-Origin: http://xxx.xxx.com(许多情况下,很多都会写成*)
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8

非简单请求

非简单请求时指那些对服务器有特殊要求的请求,比如请求方法是 put 或 delete,或者 content-type 的类型是 application/json。其实简单请求之外的都是非简单请求了。

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

非简单请求就是普通 HTML Form 无法实现的请求。比如 PUT 方法、需要其他的内容编码方式、自定义头之类的。

下面是一个预检请求的头部:

OPTIONS /cors HTTP/1.1
Origin: http://api.bob.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

一旦服务器通过了"预检"请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样了。

JSONP

JSONP 的原理就是利用 <script> 标签的 src 属性没有跨域的限制,通过指向一个需要访问的地址,由服务端返回一个预先定义好的 Javascript 函数的调用,并且将服务器数据以该函数参数的形式传递过来,此方法需要前后端配合完成。

//定义获取数据的回调方法
function getData(data) {
  console.log(data);
}

// 创建一个script标签,并且告诉后端回调函数名叫 getData
var body = document.getElementsByTagName('body')[0];
var script = document.gerElement('script');
script.type = 'text/javasctipt';
script.src = 'demo.js?callback=getData';
body.appendChild(script);

//script 加载完毕之后从页面中删除,否则每次点击生成许多script标签
script.onload = function () {
  document.body.removeChild(script);
}

JSONP 使用简单且兼容性不错,但是只限于 get 请求。

服务器代理

浏览器有跨域限制,但是服务器不存在跨域问题,所以可以由服务器请求所要域的资源再返回给客户端。(websocket)

document.domain

该方式只能用于二级域名相同的情况下,比如 a.test.com 和 b.test.com 适用于该方式。
只需要给两个页面都添加 document.domain = 'test.com',通过在 a.test.com 创建一个 iframe,去控制 iframe 的 window,从而进行交互。

postMessage

window.postMessage 是一个 HTML5 的 api,允许两个窗口之间进行跨域发送消息。参考MDN的postMessage API详解

这种方式通常用于获取嵌入页面中的第三方页面数据。一个页面发送消息,另一个页面判断来源并接收消息

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容