跨域的基础知识和细节问题

直接看这个

什么是跨域?

如果 url A 与 url B 不同源,那么页面A不能获取页面B的资源。
这里有两个关键词:url 和 同源,浏览器的同源策略就是针对两个url,它们满足以下三个条件,才是同源:

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

举例来说,http://www.example.com/dir/page.html这个网址,协议是http://,域名是域名是包括//到/的所有部分www.example.com,端口是80(默认端口可以省略)。它的同源情况如下。

http://www.example.com/dir2/other.html:同源
http://example.com/dir/other.html:不同源(域名不同)
http://v2.www.example.com/dir/other.html:不同源(域名不同)
http://www.example.com:81/dir/other.html:不同源(端口不同)

在这里插入图片描述

同源政策的目的,是为了保证用户信息的安全,防止恶意的网站窃取数据。
如果一个页面中的JS可以任意发起http跨域获取其他页面上的资源,这是一件很可怕的事,举个例子:

张三访问了某银行网站 abank.com,服务器端验证后会在响应头中加入Set-Cookie字段,然后下次张三再发起请求,浏览器会自动将cookie附加在HTTP请求的首部字段Cookie中,服务器端就知道张三已经登录过了。
张三接着访问了危险页面 danger.com,这个页面中写了一些Ajax,它发起请求访问abank.com,这一行为用户不能察觉,如果可以跨域发起请求,那么浏览器同样会自动将cookie附加在HTTP请求的首部字段Cookie中,这样这个危险危险网站就登陆了张三的银行账户。

所以用同源策略来限制跨域是必须的。
这里我思考了一个问题,在浏览器地址栏里输入url为什么没有出现跨域问题?
要明确,同源策略是浏览器的行为,在地址栏中输入url是用户主观行为,所以浏览器是不判为跨域的。那么同源限制的对象其实是页面中发起http请求的js。那同源限制的行为有哪些呢?有以下三种:

  • Cookie、LocalStorage 和 IndexDB 无法读取。
  • DOM 无法获得。
  • AJAX 请求不能获取到响应
    请求跨域了,那么请求到底发出去没有?
    跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。

注意第三点:跨域的AJAX请求其实是可以发送出去的,但是浏览器拦截了响应,使其不能获取到数据。以上三点其实都是在限制js脚本跨域获取不同域页面上的数据。

有个特例是form表单,form表单提交数据是不受跨域限制的,因为提交表单数据是没有返回值的,没有获取数据的操作,浏览器认为这样的操作是无害的,这也是csrf攻击能够实施的原因。

但是有三个标签是允许跨域加载资源:

<img src=XXX>
<link href=XXX>
<script src=XXX>

接下来说说规避跨域限制的几种方式:

针对AJAX的跨域

JSONP

JSONP利用的的是:<script><img>这些标签下载url中的资源是没有跨域限制的。

它的基本做法是:网页添加一个<script>元素,向服务器请求 JSON 数据,这种做法不受同源政策限制;服务器收到请求后,将JSON放在一个指定名字的回调函数里传回来
所以,顾名思义,JSONP = JSON with padding:填充式 JSON,其实就是服务器端把 JSON 作为回调函数的参数,返回这个回调函数。

<script type="text/javascript">
  function foo(data) {
    console.log('Your public IP address is: ' + data.ip);
};
<script>

<script type="text/javascript" src="http://example.com/ip?callback=foo"></script>

上面的script标签向服务器example.com发出请求,该请求有一个查询字符串callback参数,用来指定回调函数的名字;
服务器发现请求中有callback参数,就会将JSON作为指定回调函数的参数,回调函数作为脚本返回;
脚本返回后,会直接作为代码运行,这时,只要浏览器定义了foo函数,该函数就会立即调用。

JSONP的缺点在于只能发GET请求

jsonp只能用get原因?
如果看过 JSONP 库的源码就知道,常见的实现代码其实就是 document.createElement(‘script’) 生成一个 script 标签,然后插 body 里而已。在这里根本没有设置请求格式的余地
因为<script>标签,只支持GET

CORS

CORS是跨源资源分享(Cross-Origin Resource Sharing)的缩写。它是W3C标准,是跨源AJAX请求的根本解决方法。相比JSONP只能发GET请求,CORS允许任何类型的请求。

CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。

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

浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。
只要同时满足以下两大条件,就属于简单请求。

请求方法是以下三种方法之一:
  • HEAD
  • GET
  • POST
HTTP的头信息不超出以下几种字段:
  • Accept
  • Accept-Language
  • Content-Language
  • Last-Event-ID
  • Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
    凡是不同时满足上面两个条件,就属于非简单请求。

简单请求

简单请求的CORS分为以下几步:

  1. 浏览器发现AJAX请求跨源,在请求头中添加Origin字段,它的值是http请求的源url;
  2. 服务器根据Origin的值决定是否同意这次请求;
  3. 如果Origin指定的域名在服务器许可范围内,则会在报头中添加以下三个字段
Access-Control-Allow-Origin

该字段是必须的。它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求.

Access-Control-Allow-Credentials

该字段可选。它的值是一个布尔值,表示是否允许发送Cookie。
默认情况下,Cookie不包括在CORS请求之中。
设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器,另一方面,开发者必须在AJAX请求中打开withCredentials属性。这个值也只能设为true,如果服务器不要浏览器发送Cookie,删除该字段即可。

Access-Control-Expose-Headers

该字段可选。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段。

  1. 如果如果Origin指定的域名不在服务器许可范围内,响应头中没有Access-Control-Allow-Origin,浏览器就会抛出错误,中断这个http请求。

非简单请求

非简单请求的CORS分为以下几步:

  1. 浏览器在正式请求之前,先发送一个预检请求,预检请求的请求方法是OPTIONS,请求头中带以下两个字段:
Access-Control-Request-Method

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

Access-Control-Request-Headers

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

  1. 如果服务器端预检不通过,浏览器报错;如果通过,返回的响应头中包含以下几个字段:
Access-Control-Allow-Origin

一定包含

Access-Control-Allow-Methods

一定包含,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。

Access-Control-Allow-Credentials

可选

Access-Control-Max-Age

可选,用来指定本次预检请求的有效期,单位为秒,未过期的话不用再发送预检请求。

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

WebSocket

WebSocket是一种通信协议,使用ws://(非加密)和wss://(加密)作为协议前缀。
该协议不实行同源政策,只要服务器支持,就可以通过它进行跨源通信。

postMessage

postMessage是HTML5 XMLHttpRequest Level 2中的API,且是为数不多可以跨域操作的window属性之一,它可用于解决以下方面的问题:

  1. 页面和其打开的新窗口的数据传递
  2. 多窗口之间消息传递
  3. 页面与嵌套的iframe消息传递
  4. 上面三个场景的跨域数据传递

postMessage()方法允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档、多窗口、跨域消息传递。

otherWindow.postMessage(message, targetOrigin, [transfer]);

  • message: 将要发送到其他 window的数据。
  • targetOrigin:通过窗口的origin属性来指定哪些窗口能接收到消息事件,其值可以是字符串"*"(表示无限制)或者一个URI。在发送消息的时候,如果目标窗口的协议、主机地址或端口这三者的任意一项不匹配targetOrigin提供的值,那么消息就不会被发送;只有三者完全匹配,消息才会被发送。
  • transfer(可选):是一串和message 同时传递的 Transferable 对象.
    这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权。

针对Cookie

Cookie 是服务器写入浏览器的一小段信息,只有同源的网页才能共享。但是,两个网页一级域名相同,只是二级域名不同,浏览器允许通过设置document.domain共享 Cookie。
举例来说,A网页是http://w1.example.com/a.html,B网页是http://w2.example.com/b.html,那么只要在两个网页的脚本中设置的document.domain='example.com',两个网页就可以共享Cookie。

针对iframe

如果两个网页不同源,就无法拿到对方的DOM。

典型的例子是iframe窗口window.open方法打开的窗口,它们与父窗口无法通信。

对于完全不同源的网站,目前有三种方法,可以解决跨域窗口的通信问题:

  • 片段识别符(fragment identifier)
  • window.name
  • 跨文档通信API(Cross-document messaging),HTML5中的window.postMessage
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值