Javascript 跨域总结

2016/03/09 corsjsonpiframe

# 什么情况会跨域

  1. 主域不同
  2. 主域相同,子域不同
  3. 域名相同,协议不同(http / https)
  4. 域名相同,端口不同

# 跨域解决办法

# 1. document.domain + iframe

对于主域相同而子域不同的例子,可以通过设置 document.domain 的办法来解决。具体的做法如下:

是可以在 a.com/a.html 和 1.a.com/b.html 两个文件中分别加上 document.domain = 'a.com'; 然后通过 a.html 文件中创建一个 iframe,去控制 iframe 的 contentDocument,当然这种办法只能解决主域相同而二级域名不同的情况。

<!-- a.com/a.html -->
<iframe id='i' src="1.a.com" onload="do()"></iframe>
<script>
    document.domain = 'a.com';
    document.getElementById('i').contentWindow;
</script>
<!-- 1.a.com/b.html -->
<script>
    document.domain = 'a.com';
</script>

这样,就可以解决问题了。值得注意的是:document.domain 的设置是有限制的,只能设置为页面本身或者更高一级的域名。利用这种方法是极其方便的,但是如果一个网站被攻击之后另外一个网站很可能会引起安全漏洞

# 2. 动态创建 script (jsonp)

JSONP 的全称是 "JSON With Padding", 词面意思上理解就是 "填充式的JSON"。由于浏览器的同源策略,使得在网页端出现了这个“跨域”的问题,然而我们发现,所有的 src 属性并没有受到相关的限制,比如 img / script 等,jsonp 就是利用了 script 可以执行其它域的 js 函数,比如这样:

<!-- a.com/a.html -->
...
<script>
    function callback(data) {
        console.log(data.url)
    }
</script>

<script src='http://b.com/b.js'></script>
...


<!-- b.com/b.js -->
callback({url: 'http://www.rccoder.net'})

利用这一点,假如 b.js 里面的内容不是固定的,而是根据一些东西自动生成的, 嗯,这就是 JSONP 的主要原理了。回调函数+数据就是 JSON With Padding 了,回调函数用来响应应该在页面中调用的函数,数据则用来传入要执行的回调函数,至于这个数据是怎么产生的,无非就是服务端的字符串拼接了。

以下是 jsonp 请求的实现:

var loadJsonp = (function() {
    var seq = new Date() * 1;

    return function(url, params, callback) {
        var funName = 'XYJsonp' + seq++,
            head = document.getElementsByTagName('head')[0],
            script = document.createElement('script');

        for (var key in params) {
            url += (/\?/.test(url) ? '&': '?') + key + '=' + encodeURIComponent(params[key]);
        }

        url += '&callback=' + funName;

        window[funName] = function(data) {
            window[funName] = undefined;
            try {
                delete window[funName];
            }
            catch(e) {}

            if (head) {
                head.removeChild(script);
            }
            callback(data);
        };

        script.charset = "UTF-8";
        script.src = url;
        head.appendChild(script);
    };
} ());

注意:JSONP的这种实现方式不受同源策略的影响,兼容性也很好;但是它之支持 GET 方式的请求,只支持 HTTP 请求这种特殊的情况,对于两个不同域之间两个页面的互相调用也是无能为力。

# 3. 利用 iframe 和 location.hash

这个办法比较绕,把数据的变化显示在 url 的 hash 里面。原理是利用 location.hash 来进行传值。但是由于 chrome 和 IE 不允许修改 parent.location.hash 的值,所以需要再加一层。

a.html 和 b.html 进行数据交换:

<!-- a.com/a.html -->
<script>
    function startRequest() {
        var ifr = document.createElement('iframe');
        ifr.style.display = 'none';
        ifr.src = 'http://b.com/b.html#paramdo';
        document.body.appendChild(ifr);
    }

    function checkHash() {
        try {
            var data = location.hash ? location.hash.substring(1) : '';
            if (console.log) {
                console.log('Now the data is '+data);
            }
        }
        catch(e) {};
    }
    setInterval(checkHash, 2000);
</script>
<!-- b.com/b.html -->
<script>
    // 模拟一个简单的参数处理操作
    switch(location.hash){
        case '#paramdo':
            callBack();
            break;
        case '#paramset':
            //do something……
            break;
    }

    function callBack() {
        try {
            parent.location.hash = 'somedata';
        }
        catch (e) {
            // ie、chrome 的安全机制无法修改 parent.location.hash,
            // 所以要利用一个中间域下的代理 iframe
            var ifrproxy = document.createElement('iframe');
            ifrproxy.style.display = 'none';
            ifrproxy.src = 'http://a.com/c.html#somedata'; // 注意该文件在 a.com 域下
            document.body.appendChild(ifrproxy);
        }
    }
</script>
<!-- a.com/c.html -->
<script>
    // 因为 parent.parent 和自身属于同一个域,所以可以改变其 location.hash 的值
    parent.parent.location.hash = self.location.hash.substring(1);
</script>

# 4. window.name 实现跨域数据传输

window.name 在一个窗口(标签)的生命周期之内是共享的,利用这点结合 iframe:当在 iframe 中加载新页面时,name 的属性值依旧保持不变。

总结起来即:iframe 的 src 属性由外域转向本地域,跨域数据即由 iframe 的 window.name 从外域传递到本地域。这个就巧妙地绕过了浏览器的跨域访问限制,但同时它又是安全操作。

需要3个文件:a.com/a.html, a.com/proxy.html, b.com/b.html

<!-- a.com/a.html -->
<script type="text/javascript">
    var state = 0,
        iframe = document.createElement('iframe'),
        loadfn = function() {
            if (state === 1) {
                var data = iframe.contentWindow.name;    // 读取数据
                alert(data);    //弹出'I was there!'
            }
            else if (state === 0) {
                state = 1;
                iframe.contentWindow.location = "http://a.com/proxy.html";    // 设置的代理文件
            }
        };

    iframe.src = 'http://b.com/b.html';

    if (iframe.attachEvent) {
        iframe.attachEvent('onload', loadfn);
    }
    else {
        iframe.onload  = loadfn;
    }

    document.body.appendChild(iframe);
</script>
<!-- b.com/b.html -->
<script type="text/javascript">
    window.name = 'I was there!';
    // 这里是要传输的数据,大小一般为2M,IE和firefox下可以大至32M左右
    // 数据格式可以自定义,如json、字符串
</script>

proxy 是一个代理文件,空的就可以,需要和 a 在同一域下

# 5. 利用 HTML5 postMessage

window.postMessage 是 HTML5 新增 API 之一,可以用来向其他所有的 window 对象发送消息。需要注意的是我们必须要保证所有的脚本执行完才发送 MessageEvent,如果在函数执行的过程中调用了他,就会让后面的函数超时无法执行。

下一代浏览器支持这个功能 Chrome 2.0+, Internet Explorer 8.0+, Firefox 1.0+, Opera 9.6+, 和 Safari 4.0+ 。

参考资料

# 6. CORS

CORS 的全称是 Cross-Origin Resource Sharing,即跨域资源共享。他的原理就是使用自定义的 HTTP 头部,让服务器与浏览器进行沟通,主要是通过设置响应头的 Access-Control-Allow-Origin 来达到目的,这样,XMLHttpRequest 就能跨域了。

参考资料

上次更新: 2023/1/2 02:25:24