跨域

本文介绍了浏览器的同源策略,解释了跨域的概念及其必要性,并详细讲解了多种跨域实现方式,包括document.domain、location.hash、window.name、window.postMessage、LocalStorage、JSONP、WebSocket、CORS等。

按照顺序,我们需要先知道什么是跨域(cross-origin)?为什么要跨域?怎么跨域?

首先需要了解浏览器的“同源策略”(same-origin policy):同源策略是浏览器的一个安全功能,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源。而所谓的“源”(origin)指的是:协议,域名,端口号。

然后,对于网络小白,需要补充一下域名(domain name)的知识点。百度告诉我们域名是由一串用点分隔的名字组成的internet上某一台计算机或计算机组的名称,用于在数据传输时标识计算机的电子方位。一个完整的域名由2个或2个以上的部分组成,个部分之间用“.”来分隔,最后一个“.”右边的部分称为顶级域名,左边部分称为二级域名,二级域名的左边部分称为三级域名,以此类推。每一级域名控制它下一级域名的分配。

那么,受同源策略的限制,如果不是同源的脚本,就不能操作其他源下面的对象,想要操作另一个对象,就需要跨域。

受同源策略的限制,以下几种行为无法实现:

(1)Cookie、LocalStorage、IndexDB取法读取

(2)DOM无法获得

(3)AJAX请求不能发送

当然,也有不受同源策略限制的情况:

(1)页面中的链接、重定向和表单的提交

(2)可以引入跨域的资源,但是不能通过JS读写。(例如<script src=..></script>,<img>,<link>,<iframe>等)

跨域的实现方式有很多种,下面一一讲解:

1.降域document.domain

同源策略认为主域相同,子域不同非同源,如:

a.example.com和example.com

a.example.com和b.example.com

b.a.example.com和a.example.com

以上均是两两不同源,可以设置document.domain="example.com",那么浏览器就会认为它们都是同一个源。要实现上述任意两个页面的通信,两个页面必须都设置document.domain="example.com"。

特点:

(1)document.domain的设置是有限制的,只能设置成自身或更高一级的父域,且主域名必须相同。例如a.b.example.com中某文档的document.domain只能设置成a.b.example.com、b.example.com、example.com中的任意一个。

(2)存在安全性问题,当一个站点被攻击后,另一个站点会引起安全漏洞

(3)这种方法只适用于Cookie和iframe窗口

  • 针对Cookie:AB网页均设置相同document.domain,A网页js设置document.cookie='xxx',那么B网页可以读取var allCookie=document.cookie
  • 针对iframe:iframe窗口和window.open方法打开的窗口无法与父窗口通信。针对两个窗口一级域名相同,二级域名不同(即域名和子域名不同的情况),可以设置相同document.domain实现跨域通信。

2.设置片段标识符,读取location.hash(iframe)

片段标识符(fragment identifier)指的是URL的“#”后面的部分,比如http://example.com/x.html#fragment的#fragment。如果只是改变片段标识符,页面不会重新刷新。

父窗口就可以把信息写入子窗口的片段标识符,子窗口通过监听hashchange事件得到通知:

var src=originURL+'#'+data;
document.getElementById('myFragment').src=src; //#myFragment为子窗口

window.onhashchange=checkMessage;
function checkMessage(){
	var message=window.location.hash;
}

同样的,子窗口也可以改变父窗口的片段标识符:

parent.location.href=target+"#"+hash;

特点:

(1)容量有限

(2)由于URL的内容可视,存在泄露信息的问题

3.window.name(iframe)

浏览器窗口有window.name属性,这个属性的最大特点就是,无论是否同源,只要在同一个窗口里,前一个网页设置了这个属性后,后一个网页就可以读取它。

父窗口先打开一个子窗口,载入一个不同源的子网页,子网页将信息写入window.name属性:

window.name=data;

接着,子窗口跳回一个与主窗口同域的网址:

location='http://parent.url.com/xxx.html';

然后,主窗口就可以读取子窗口的window.name了:

var data=document.getElementById('myFragment').contentWindow.name;

特点:

(1)window.name的值只能是字符串,容量很大(2M),可以放置非常长的数据

(2)需要监听子窗口的window.name属性变化,一定程度上影响了网页性能

4.window.postMessage(iframe)

HTML5针对跨域问题,引入了一个全新的API:跨文档通信API(Cross-document messaging)。这个API为window对象新增了一个window.postMessage()方法,允许跨窗口通信。

举例来说,父窗口http://aaa.com向子窗口http://bbb.com发消息,调用window.postMessage方法即可:

var popup=window.open('http://bbb.com','title');
popup.postMessage('Hello world!','http://bbb.com');

postMessage()方法的第一个参数是信息内容,第二个参数是接收消息的窗口的源(origin),也可设置为“*”,表示不限制域名,向所有窗口发送。

子窗口向父窗口发送消息:

window.opener.postMessage('Nice to meet you','http://aaa.com')

另外,父窗口和子窗口都可以通过监听器监听message事件来监听对方的消息:

window.addEventListener('message',function(e){
	console.log(e.source); //发送消息的窗口
	console.log(e.origin); //消息发向的网址
	console.log(e.data); //消息内容
	e.source.postMessage('Nice to meet you','*'); //子窗口通过event.source属性引用父窗口,然后发送信息
},false);

5.LocalStorage

通过window.postMessage,读写其他窗口的localStorage。

6.JSONP(AJAX)

同源策略规定,AJAX请求只能发送给同源的网址,否则报错。JSONP是服务器与客户端跨域通信的常用方法。

它的基本思想是,网页通过添加一个<script>元素,向服务器请求JSON数据,这种做法不受同源策略限制;服务器收到请求后,将数据放在一个指定名字的回调函数里传回来。

首先,网页动态插入<script>元素,由它向跨源网址发出请求:

function addScriptTag(src){
	var script=document.createElement('script');
	script.setAttribute('type','text/javascript');
	script.src=src;
	document.body.appendChild(script);
}

window.onload=function(){
	addScriptTag('http://example.com/ip?callback=foo');
}

function foo(data){
	console.log('Your public IP address is: '+ data.ip);
}

上面代码通过动态添加<script>元素来向服务器example.com发出请求。该请求的查询字符串有一个callback参数,用来指定回调函数的名字,这对于JSONP是必须的。

服务器收到这个请求后,会将数据放在回调函数的参数位置返回:

foo({
	"ip":"8.8.8.8"
});
由于元素<script>请求的脚本,直接作为代码执行,所以只要浏览器定义了foo函数,该函数就会立即调用。作为参数的JSON数据就会被视为JavaScript对象,而不是字符串,因此避免了使用JSON.parse()

特点:

(1)简单实用,老式浏览器全部支持,服务器改造非常小

7.WebSocket(AJAX)

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

下例是浏览器发出的WebSocket请求的头信息:


上面代码中,有一个字段是Origin,表示该请求的请求源(origin),即发自哪个域名。

正是因为有了Origin字段,所以WebSocket才没有实行同源政策。因为服务器可以根据这个字段,判断是否许可本次通信。如果该域名在白名单内,服务器就会做出如下回应:


8.CORS(AJAX)

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

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

首先需要了解什么是简单请求:

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


本文暂时只针对讲解简单请求,浏览器直接发出CORS请求,具体来说,就是在头信息中增加一个Origin字段。

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


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

如果Origin指定的源不在许可范围内,服务器会返回一个正常的HTTP回应。浏览器发现这个回应的头信息没有包含Access-Control-Allow-Origin字段,就会抛出一个错误并被XMLHttpRequest的onerror回调函数捕获。

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


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

  • Access-Control-Allow-Origin(必须):它的值要么是请求时的Origin值,要么是*,表示接受任意域名的请求
  • Access-Control-Allow-Credentials(可选):是一个布尔值,表示是否允许发送Cookie,默认Cookie不包括在CORS请求中。这个值也只能设为true(表示Cookie可以包含在请求中一起发送给服务器),如果不要浏览器发送Cookie,删除该字段即可。
  • 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字段的值。

特点:

(1)IE8以下不支持,IE8-10部分支持,而CORS需要浏览器和服务器同时支持

(2)与JSONP相比,CORS更强大,JSONP只支持GET请求,但是CORS支持所有类型的HTTP请求。

(3)JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值