跨域详细梳理

什么是跨域

跨域一词从字面意思看,就是跨域名嘛,但实际上跨域的范围绝对不止那么狭隘。具体概念如下:只要协议、域名、端口有任何一个不同,都被当作是不同的域。之所以会产生跨域这个问题呢,其实也很容易想明白,要是随便引用外部文件,不同标签下的页面引用类似的彼此的文件,浏览器很容易懵逼的,安全也得不到保障了就。什么事,都是安全第一嘛。但在安全限制的同时也给注入iframe或是ajax应用上带来了不少麻烦。所以我们要通过一些方法使本域的js能够操作其他域的页面对象或者使其他域的js能操作本域的页面对象(iframe之间)。下面是具体的跨域情况详解:

URL                      说明       是否允许通信
http://www.a.com/a.js
http://www.a.com/b.js     同一域名下   允许

http://www.a.com/lab/a.js
http://www.a.com/script/b.js 同一域名下不同文件夹 允许

http://www.a.com:8000/a.js
http://www.a.com/b.js     同一域名,不同端口  不允许

http://www.a.com/a.js
https://www.a.com/b.js 同一域名,不同协议 不允许

http://www.a.com/a.js
http://70.32.92.74/b.js 域名和域名对应ip 不允许

http://www.a.com/a.js
http://script.a.com/b.js 主域相同,子域不同 不允许(cookie这种情况下也不允许访问)

http://www.a.com/a.js
http://a.com/b.js 同一域名,不同二级域名(同上) 不允许(cookie这种情况下也不允许访问)

http://www.cnblogs.com/a.js
http://www.a.com/b.js 不同域名 不允许

有src的标签可实现跨域

所有具有src属性的HTML标签都是可以跨域的,包括, ,该方法需要创建一个DOM对象,且只能用于GET方法。

在document.body中append一个具有src属性的HTML标签,src属性值指向的URL会以GET方法被访问,该访问是可以跨域的。其实样式表的标签也是可以跨域的,只要是有src或href的HTML标签都有跨域的能力。

不同的HTML标签发送HTTP请求的时机不同,例如在更改src属性时就会发送请求,而script, iframe, link[rel=stylesheet]只有在添加到DOM树之后才会发送HTTP请求:

var img = new Image();
img.src = 'http://some/picture';        // 发送HTTP请求

var ifr = $('<iframe>', {src: 'http://b.a.com/bar'});
$('body').append(ifr);                  // 发送HTTP请求

设置document.domain跨域

相同主域名不同子域名下的页面,可以设置document.domain让它们同域,同域document提供的是页面间的互操作,需要载入iframe页面。

// 在www.a.com/a.html中
document.domain = 'a.com';
var ifr = document.createElement('iframe');
ifr.src = 'http://www.script.a.com/b.html';
ifr.display = none;
document.body.appendChild(ifr);
ifr.onload = function(){
    var doc = ifr.contentDocument || ifr.contentWindow.document;
    //在这里操作doc,也就是b.html
    ifr.onload = null;
};
// 在www.script.a.com/b.html中
document.domain = 'a.com';

使用jsonp进行跨域

jsonp = json(json数据) + padding(填充)
其实对于常用性来说,jsonp应该是使用最经常的一种跨域方式了,他不受浏览器兼容性的限制。但是他也有他的局限性,只能发送 GET 请求,需要服务端和前端规定好,写法丑陋。它的原理在于浏览器请求 script 资源不受同源策略限制,并且请求到 script 资源后立即执行。

浏览器端

首先全局注册一个callback回调函数,记住这个函数名字(比如:resolveJson),这个函数接受一个参数,参数是期望的到的服务端返回数据,函数的具体内容是处理这个数据。然后动态生成一个 script 标签,src 为:请求资源的地址+获取函数的字段名+回调函数名称,这里的获取函数的字段名是要和服务端约定好的,是为了让服务端拿到回调函数名称。(如:www.qiute.com?callbackName=resolveJson)。

function resolveJosn(result) {
    console.log(result.name);
}
var jsonpScript= document.createElement("script");
jsonpScript.type = "text/javascript";
jsonpScript.src = "https://www.qiute.com?callbackName=resolveJson";
document.getElementsByTagName("head")[0].appendChild(jsonpScript);

服务端

在接受到浏览器端 script 的请求之后,从url的query的callbackName获取到回调函数的名字,例子中是resolveJson。然后动态生成一段javascript片段去给这个函数传入参数执行这个函数。比如:

resolveJson({name: 'qiutc'});

执行

服务端返回这个 script 之后,浏览器端获取到 script 资源,然后会立即执行这个 javascript,也就是上面那个片段。这样就能根据之前写好的回调函数处理这些数据了。

window.name跨域

window对象有个name属性,该属性有个特征:即在一个窗口(window)的生命周期内,窗口载入的所有的页面都是共享一个 window.name 的,每个页面对 window.name 都有读写的权限,window.name 是持久存在一个窗口载入过的所有页面中的,并不会因新页面的载入而进行重置。比如有一个www.qiutc.me/a.html页面,需要通过a.html页面里的js来获取另一个位于不同域上的页面www.qiutc.com/data.html里的数据。data.html页面里的代码很简单,就是给当前的window.name设置一个a.html页面想要得到的数据值。data.html里的代码:

<script>
window.name = '我是被期望得到的数据';
</script>

那么在 a.html 页面中,我们怎么把 data.html 页面载入进来呢?显然我们不能直接在 a.html 页面中通过改变 window.location 来载入data.html页面(这简直扯蛋)因为我们想要即使 a.html页面不跳转也能得到 data.html 里的数据。答案就是在 a.html 页面中使用一个隐藏的 iframe 来充当一个中间人角色,由 iframe 去获取 data.html 的数据,然后 a.html 再去得到 iframe 获取到的数据。充当中间人的 iframe 想要获取到data.html的通过window.name设置的数据,只需要把这个iframe的src设为www.qiutc.com/data.html就行了。然后a.html想要得到iframe所获取到的数据,也就是想要得到iframe的window.name的值,还必须把这个iframe的src设成跟a.html页面同一个域才行,不然根据前面讲的同源策略,a.html是不能访问到iframe里的window.name属性的。这就是整个跨域过程。

// a.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <script>
    function getData() {
        var iframe =document.getElementById('iframe');
        iframe.onload = function() {
            var data = iframe.contentWindow.name; // 得到
        }
        iframe.src = 'b.html';  // 这里b和a同源
    }
  </script>
</head>
<body>
  <iframe src="https://www.qiutc.com/data.html" style="display:none" onload="getData()"</iframe>
</body>
</html>

window.postMessage跨域

HTML5允许窗口之间发送消息,浏览器需要支持HTML5,获取窗口句柄后才能相互通信。这是一个安全的跨域通信方法,postMessage(message,targetOrigin)也是HTML5引入的特性。可以给任何一个window发送消息,不论是否同源。第二个参数可以是*但如果你设置了一个URL但不相符,那么该事件不会被分发。看一个普通的使用方式吧:

// URL: http://a.com/foo
var win = window.open('http://b.com/bar');
win.postMessage('Hello, bar!', 'http://b.com');
// URL: http://b.com/bar
window.addEventListener('message',function(event) {
    console.log(event.data);
});

跨域资源共享(CORS)

服务器设置Access-Control-Allow-OriginHTTP响应头之后,浏览器将会允许跨域请求,浏览器需要支持HTML5,可以支持POST,PUT等方法。前面提到的跨域手段都是某种意义上的Hack, HTML5标准中提出的跨域资源共享(Cross Origin Resource Share,CORS)才是正道。 它支持其他的HTTP方法如PUT, POST等,可以从本质上解决跨域问题。

例如,从http://a.com要访问http://b.com的数据,通常情况下Chrome会因跨域请求而报错:

XMLHttpRequest cannot load http://b.com. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://a.com' is therefore not allowed access.

错误原因是被请求资源没有设置Access-Control-Allow-Origin,所以我们在b.com的服务器中设置这个响应头字段即可:

Access-Control-Allow-Origin: *              # 允许所有域名访问,或者
Access-Control-Allow-Origin: http://a.com   # 只允许所有域名访问