浏览器的跨域问题以及解决方案(转载)

解决跨域一:Cookie如何实现跨域

  • Cookie是服务器写入浏览器的一段信息,只有同源的网页才能共享,但是,两个网页一级域名相同,只是二级域名不同,浏览器允许通过设置document.domain共Cookie。
  • 举例来说,A网站是:http:weibo.qq.com;B网站是:http:lol.qq.com;那么只需设置相同的document.domain,两个网页就可共享Cookie。

    1
    document.domain = 'qq.com';
  • 现在,A网页通过脚本设置一个Cookie。

    1
    document.Cookie = "test1=hello";
  • B网页就能读到这个Cookie。

    1
    var getCookie = document.cookie;

注意:这种方法只适用于Cookie和iframe窗口,LocalStorage和IndexDB无法通过这种方法规避,而要使用下文将介绍的PostMessage API。
另外,服务器也可以在设置Cookie的时候,指定Cookie的所属域名为一级域名,比如:.qq.com

1
Set-Cookie:key=value;domain=<span style="color:#cc0000;">.qq.com</span>;path=/
  • 这样的话,二级域名和三级域名不用做任何设置,都可以读取这个Cookie。

解决跨域问题二:如何跨域获取DOM。

  • 如果两个网页不同源,就无法拿到对方的DOM。典型的例子是iframe窗口和window.open方法打开的窗口,它们与父窗口无法通信。
  • 比如,父窗口运行下面的命令,如果iframe窗口不是同源将会报错。

    1
    document.getElementById("iframe").contentWindow.document
  • 上面命令中,父窗口想获取子窗口的DOM,应为跨源导致报错。反之亦然,子窗口获取主窗口的DOM也会报错。

1
window.parent.document.body
  • 如果两个窗口一级域名相同,只是二级域名不同,那么设置4-1介绍的document.domain属性,就可规避同源政策,拿到DOM。
  • 对于完全不相同的网站,目前有三种方法,可以解决跨域窗口的通信问题。
    • (1)片段识别符(fragment identifier)
    • (2)window.name
    • (3)跨文档通信API(Cross-document messaging)

1)片段识别符

  • 片段识别符指的是,URL的#号后面的部分,比如http://qq.com/x.html#fragment的#fragment。如果只是改变片段标识符,页面将不会重新刷新、父窗口可以把信息,写入子窗口的片段标识符。

    1
    2
    var src = originURL+'#'+data;
    document.getElementById('iframe').src = src;
  • 子窗口通过监听hashchange事件得到通知

    1
    2
    3
    4
    5
    window.onhashchange = checkMessage;
    function checkMessage(){
    var message = window.location.hash;
    //...
    }
  • 同样的,子窗口也可以改变父窗口的片段标识符。

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

2)window.name

  • 浏览器窗口有window.name属性。这个属性的最大特点是,无论是否同源,只要在同一个窗口里,前一个网页设置这个属性,后一个网页就可以读取它。
  • 父窗口先发开一个子窗口,载入一个不同源的网页,该网页将信息写入window.name属性。

    1
    window.name = data;
  • 接着,子窗口跳回一个与主窗口同域的网址。

    1
    location = 'http://parent.url.com/xxx.html';
  • 然后,主窗口就可以读取子窗口的window.name了。

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

优点

  • window.name容量很大,可以防止非常长的字符串;

    缺点

  • 必须监听子窗口window.name属性的变化,会影响网页性能。

3)跨文档消息传输window.postMessage:

  • 上面两种方法都属于破解,HTML5为解决这个问题,引入一个全新的API:跨文档消息传输Cross Document Messaging。
  • 下一代浏览器都将支持这个功能:Chrome 2.0+、Internet Explorer 8.0+, Firefox 3.0+, Opera 9.6+, 和 Safari 4.0+ 。
  • Facebook已经使用了这个功能,用postMessage支持基于web的实时消息传递。

使用方法

  • otherWindow.postMessage(message, targetOrigin);
    • otherWindow: 对接收信息页面的window的引用。可以是页面中iframe的contentWindow属性;window.open的返回值;
      通过name或下标从window.frames取到的值。
    • message: 具体的信息内容,string类型。
      targetOrigin: 接受消息的窗口的源(origin),即“协议+域名+端口”。也可以设为“*”,表示不限制域名,向所有窗口发送。
    • message事件的事件对象event,提供一下三个属性:
      • (1).event.source:发送消息的窗口
      • (2).event.origin:消息发向的网站
      • (3).event.data:消息内容

具体实例:

  • a.com/index.html中的代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <iframe id="ifr" src="b.com/index.html"></iframe>
    <script type="text/javascript">
    window.onload = function() {
    var ifr = document.getElementById('ifr');
    var targetOrigin = 'http://b.com'; // 若写成'http://b.com/c/proxy.html'效果一样
    // 若写成'http://c.com'就不会执行postMessage了
    ifr.contentWindow.postMessage('I was there!', targetOrigin);
    };
    </script>
  • b.com/index.html中的代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <script type="text/javascript">
    window.addEventListener('message', function(event){
    // 通过origin属性判断消息来源地址
    if (event.origin == 'http://a.com') {
    alert(event.data); // 弹出"I was there!"
    alert(event.source); // 对a.com、index.html中window对象的引用
    // 但由于同源策略,这里event.source不可以访问window对象
    }
    }, false);
    </script>

如何解决跨域LocalStorage。

  • 通过window.postMessage,读写其他窗口的LocalStorage也成为了可能。下面是一个例子,主窗口写入iframe子窗口的LocalStorage。

    1
    2
    3
    4
    5
    6
    7
    window.onmessage = function(e){
    if(e.origin !== 'http://bbb.com'){
    return ;
    }
    var payload = JSON.parse(e.data);
    localStorage.setItem(payload.key,JSON.stringify(payload.data));
    };
  • 上面代码中,子窗口将父窗口发送来的消息,写入自己的LocalStorage。

  • 父窗口发送消息的代码如下.

    1
    2
    3
    var win = document.getElementsByTagName('iframe')[0].contentWindow;
    var obj = {name:'Jack'};
    win.postMessage(JSON.stringify({key:'storage',data:obj}),'http://bbb.com');
  • 加强版的子窗口接受消息的代码如下。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    window.onmessage = function(e) {
    if (e.origin !== 'http://bbb.com') return;
    var payload = JSON.parse(e.data);
    switch (payload.method) {
    case 'set':
    localStorage.setItem(payload.key, JSON.stringify(payload.data));
    break;
    case 'get':
    var parent = window.parent;
    var data = localStorage.getItem(payload.key);
    parent.postMessage(data, 'http://aaa.com');
    break;
    case 'remove':
    localStorage.removeItem(payload.key);
    break;
    }
    };
  • 加强版的父窗口发送消息代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var win = document.getElementsByTagName('iframe')[0].contentWindow;
    var obj = { name: 'Jack' };
    // 存入对象
    win.postMessage(JSON.stringify({key: 'storage', method: 'set', data: obj}), 'http://bbb.com');
    // 读取对象
    win.postMessage(JSON.stringify({key: 'storage', method: "get"}), "*");
    window.onmessage = function(e) {
    if (e.origin != 'http://aaa.com') return;
    // "Jack"
    console.log(JSON.parse(e.data).name);
    };

如何解决AJAX的跨域:

  • 同源政策规定,AJAX请求只能发给同源的网址,否则就报错。
  • 除了架设服务器代理(浏览器请求同源服务器,再由后者请求外部服务),有三种方法规避这个限制。
    • (1)JSONP
    • (2)WebSocket
    • (3)CORS

1)JSONP解决AJAX跨域问题:

  • JSONP是服务器与客户端跨源通信的常用方法。最大特点就是简单适用,老式浏览器全部支持,服务器改造非常小。
  • 它的基本思想是,网页通过添加一个<script>元素,向服务器请求JSON数据,这种做法不受同源政策限制;服务器收到请求后,将数据放在一个指定名字的回调函数里传回来。
  • 首先,网页动态插入<script>元素,由它向跨源网址发出请求。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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是必需的。

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

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

2)通过webSocket解决AJAX跨域

  • WebSocket是一种通信协议,使用ws://(非加密)和wss://(加密)作为协议前缀。该协议不实行同源政策,只要服务器支持,就可以通过它进行跨源通信。
  • 下面是一个例子,浏览器发出的WebSocket请求的头信息(摘自维基百科)。

    1
    2
    3
    4
    5
    6
    7
    8
    GET /chat HTTP/1.1
    Host: server.example.com
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
    Sec-WebSocket-Protocol: chat, superchat
    Sec-WebSocket-Version: 13
    Origin: http://example.com
  • 上面代码中,有一个字段是Origin,表示该请求的请求源(origin),即发自哪个域名。
    正是因为有了Origin这个字段,所以WebSocket才没有实行同源政策。因为服务器可以根据这个字段,判断是否许可本次通信。如果该域名在白名单内,服务器就会做出如下回应。

    1
    2
    3
    4
    5
    HTTP/1.1 101 Switching Protocols
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
    Sec-WebSocket-Protocol: chat

3)通过CORS解决AJAX跨域

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

    定义

  • CORS其实出现时间不短了,它在维基百科上的定义是:跨域资源共享(CORS)是一种网络浏览器的技术规范,它为Web服务器定义了一种方式,允许网页从不同的域访问其资源。而这种访问是被同源策略所禁止的。CORS系统定义了一种浏览器和服务器交互的方式来确定是否允许跨域请求。 它是一个妥协,有更大的灵活性,但比起简单地允许所有这些的要求来说更加安全。而W3C的官方文档目前还是工作草案,但是正在朝着W3C推荐的方向前进。
  • 简言之,CORS就是为了让AJAX可以实现可控的跨域访问而生的。

以往的解决方案:

  • 以前要实现跨域访问,可以通过JSONP、Flash或者服务器中转的方式来实现,但是现在我们有了CORS。
  • CORS与JSONP相比,无疑更为先进、方便和可靠。
    • 1.JSONP只能实现GET请求,而CORS支持所有类型的HTTP请求。
    • 2.使用CORS,开发者可以使用普通的XMLHttpRequest发起请求和获得数据,比起JSONP有更好的错误处理。
    • 3.JSONP主要被老的浏览器支持,它们往往不支持CORS,而绝大多数现代浏览器都已经支持了CORS(这部分会在后文浏览器支持部分介绍)。

      详细内容:

  • 要使用CORS,我们需要了解前端和服务器端的使用方法。
  • 1.前端

    • 以前我们使用Ajax,代码类似于如下的方式:
      1
      2
      3
      var xhr = new XMLHttpRequest();
      xhr.open("GET", "/relativeHref", true);
      xhr.send();
  • 这里的“/relativeHref”是本域的相对路径。

  • 如果我们要使用CORS,相关Ajax代码可能如下所示:

    1
    2
    3
    var xhr = new XMLHttpRequest();
    xhr.open("GET", "http://blog.csdn.net/hfahe", true);
    xhr.send();
  • 请注意,代码与之前的区别就在于相对路径换成了其他域的绝对路径,也就是你要跨域访问的接口地址。

  • 我们还必须提供浏览器回退功能检测和支持,避免浏览器不支持的情况。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    function createCORSRequest(method, url) {
    var xhr = new XMLHttpRequest();
    if ("withCredentials" in xhr) {
    // 此时即支持CORS的情况
    // 检查XMLHttpRequest对象是否有“withCredentials”属性
    // “withCredentials”仅存在于XMLHTTPRequest2对象里
    xhr.open(method, url, true);
    } else if (typeof!= "undefined") {
    // 否则检查是否支持XDomainRequest,IE8和IE9支持
    // XDomainRequest仅存在于IE中,是IE用于支持CORS请求的方式
    xhr = new XDomainRequest();
    xhr.open(method, url);
    } else {
    // 否则,浏览器不支持CORS
    xhr = null;
    }
    return xhr;
    }
    var xhr = createCORSRequest('GET', url);
    if (!xhr) {
    throw new Error('CORS not supported');
    }
原文链接http://blog.csdn.net/u013084331/article/details/51114288
您的支持将鼓励我继续创作!