JavaScript的跨域访问问题

只愿长相守 提交于 2019-12-04 22:57:33

文章分享:

详解JS跨域问题
HTTP access control (CORS)
html5 postMessage解决跨域、跨窗口消息传递
跨域与跨域访问


一、什么是跨域?

概念:只要协议域名端口有任何一个不同,都被当作是不同的域。


二、为什么浏览器要限制跨域访问呢?

原因就是安全问题:如果一个网页可以随意地访问另外一个网站的资源,那么就有可能在客户完全不知情的情况下出现安全问题。


三、跨域的常用方法

1. 跨域资源共享(CORS)

服务器端对于CORS的支持,主要就是通过设置Access-Control-Allow-Origin来进行的

2. JSONP

什么是jsonp?维基百科的定义是:JSONP(JSON with Padding)

JSONP由两部分组成:回调函数数据。回调函数是当响应到来时应该在页面中调用的函数,而数据就是传入回调函数中的JSON数据。

JSONP实现原理:在js中,我们直接用XMLHttpRequest请求不同域上的数据时是不可以的。但是,在页面上引入不同域上的js脚本文件却是可以的,jsonp正是利用这个特性来实现的。

// jQuery
<script type="text/javascript">
    $.getJSON('http://example.com/data.php?callback=?,function(jsondata)'){
        //处理获得的json数据
    });
</script>

jquery会自动生成一个全局函数来替换callback=?中的问号,之后获取到数据后又会自动销毁,实际上就是起一个临时代理函数的作用。$.getJSON()方法会自动判断是否跨域,不跨域的话,就调用普通的ajax方法;跨域的话,则会以异步加载js文件的形式来调用jsonp的回调函数。

// 原生JavaScript
<script type="text/javascript">
    function dosomething(jsondata){
        //处理获得的json数据
    }
</script>
<script src="http://example.com/data.php?callback=dosomething"></script>

js文件载入成功后会执行我们在url参数中指定的函数,并且会把我们需要的json数据作为参数传入。所以jsonp是需要服务器端的页面进行相应的配合的。

<?php
    $callback = $_GET['callback']; //得到回调函数名
    $data = array('a','b','c');    //要返回的数据
    echo $callback.'('.json_encode($data).')';//输出
?>

输出结果为:dosomething(['a','b','c']);


CORS和JSONP对比

CORS与JSONP相比,无疑更为先进、方便和可靠。

1、 JSONP只能实现GET请求,而CORS支持所有类型的HTTP请求。

2、 使用CORS,开发者可以使用普通的XMLHttpRequest发起请求和获得数据,比起JSONP有更好的错误处理。

3、 JSONP主要被老的浏览器支持,它们往往不支持CORS,而绝大多数现代浏览器都已经支持了CORS)。

3. 修改document.domain来跨子域

浏览器都有一个同源策略,其限制之一就是第一种方法中我们说的不能通过ajax的方法去请求不同源中的文档。 它的第二个限制是浏览器中不同域的框架之间是不能进行js的交互操作的。修改document.domain的方法只适用于不同子域的框架间的交互。

例如:我们只要把http://www.example.com/a.htmlhttp://example.com/b.html这两个页面的document.domain都设成相同的域名‘example.com’就可以了。但要注意的是,document.domain的设置是有限制的,我们只能把document.domain设置成自身或更高一级的父域,且主域必须相同。

1.在页面 http://www.example.com/a.html 中设置document.domain:

<iframe id = "iframe" src="http://example.com/b.html" onload = "test()"></iframe>
<script type="text/javascript">
    document.domain = 'example.com';//设置成主域
    function test(){
        alert(document.getElementById('iframe').contentWindow);//contentWindow 可取得子窗口的 window 对象
    }
</script>

2.在页面 http://example.com/b.html 中也设置document.domain:

<script type="text/javascript">
    document.domain = 'example.com';
</script>


4. 通过window.name进行跨域

window.name值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的 name 值(2MB)。

name 在浏览器环境中是一个全局/window对象的属性,且当在 frame 中加载新页面时,name 的属性值依旧保持不变。通过在iframe 中加载一个资源,该目标页面将设置 frame 的 name 属性。此 name 属性值可被获取到,以访问 Web服务发送的信息。但 name 属性仅对相同域名的 frame 可访问。这意味着为了访问 name 属性,当远程 Web服务页面被加载后,必须导航 frame 回到原始域。同源策略依旧防止其他 frame 访问 name 属性。一旦 name 属性获得,销毁
frame 。

有三个页面:

  • a.com/app.html:应用页面。
  • a.com/proxy.html:代理文件,一般是一个没有任何内容的html文件,需要和应用页面在同一域下。
  • b.com/data.html:应用页面需要获取数据的页面,可称为数据页面。

实现起来基本步骤如下:

1、数据页面会把数据附加到这个iframe的window.name上

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

2、在应用页面(a.com/app.html)中创建一个iframe,把其src指向数据页面(b.com/data.html),并监听iframe的onload事件,在此事件中设置这个iframe的location指向本地域的代理文件(a.com/proxy.html, 代理文件和应用页面在同一域下,所以可以相互通信)

// a.com/app.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/data.html';
    if (iframe.attachEvent) {
        iframe.attachEvent('onload', loadfn);
    } else {
        iframe.onload  = loadfn;
    }
    document.body.appendChild(iframe);
</script>

3、获取数据以后销毁这个iframe,释放内存;这也保证了安全(不被其他域frame js访问)。

// a.com/app.html
<script type="text/javascript">
    iframe.contentWindow.document.write(''); //清空
    iframe.contentWindow.close();            //销毁
    document.body.removeChild(iframe);       //释放内存
</script>


5. 使用HTML5的window.postMessage方法跨域

window.postMessage(data, origin) 方法是html5新引进的特性,可以使用它来向其它的window对象发送消息,无论这个window对象是属于同源或不同源,目前IE8+、FireFox、Chrome、Opera等浏览器都已经支持window.postMessage方法。

  • data
    部分浏览器只能处理字符串参数,所以我们在传递参数的时候需要使用JSON.stringify()方法对对象参数序列化

  • origin
    指明目标窗口的源,协议+主机+端口号[+URL],URL会被忽略,所以可以不写。targetOrigin设置为”*”,这样可以传递给任意窗口,如果要指定和当前窗口同源的话设置为”/”

// http://test.com/index.html

<div>
    <iframe id="child" src="http://lsLib.com/lsLib.html"></iframe>
</div>

<script type="text/javascript">
    window.onload = function(){
        window.frames[0].postMessage('getcolor','http://lslib.com');
    }
</script>

test.com/index.htmllslib.com 发送了消息,在lslib.com 做出如下反应:

// http://lslib.com/lslib.html

<script type="text/javascript">
    window.addEventListener('message',function(e){
         if(e.source!=window.parent) {
          return;
         }
         var color = container.style.backgroundColor;
         window.parent.postMessage(color,'*');
    },false);
</script>

MessageEvent对象是:
这里写图片描述

有几个重要属性:

  • data:传递来的消息数据
  • source:发送消息的窗口对象
  • origin:发送消息窗口的源(协议+主机+端口号)
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!