如何解决JS跨域问题?

来源:互联网 发布:明解c语言 中级篇 pdf 编辑:程序博客网 时间:2024/06/10 23:40

什么是跨域?

一个域名地址的组成:

http:// www abc.com :8080 /script/iquire.js 协议 子域名 主域名 端口号 请求资源地址

协议、子域名、主域名、端口号中任意一个不相同时,都算作不同的域。
不同域之间相互请求资源,就算 “跨域”。

例如:
a)同一域名,不同协议 不允许通信
http://www.a.com/a.js
https://www.a.com/b.js
b)主域相同,子域不同 不允许通信
http://www.a.com/a.js
http://script.a.com/b.js
c)同一域名,不同二级域名 不允许
http://www.a.com/a.js
http://a.com/b.js

注意:
1)如果是协议端口造成的跨域问题“前台”是无能为力的
2)在跨域问题上,域仅仅是通过“URL的首部”来识别,而不会去尝试判断相同的ip地址对应着两个域或两个域是否在同一个ip上。
“URL的首部”指window.location.protocol +window.location.host,也可以理解为“Domains, protocols and ports must match”。
例如:localhost和127.0.0.1虽然都指向本机,但也属于跨域。

为什么要解决跨域问题?

跨域问题是由浏览器的同源策略造成的,是浏览器对 JavaScript 施加的安全限制。

所谓同源策略,指的是浏览器对不同源的脚本或者文本的访问方式进行的限制。
从一个域上加载的脚本不允许访问另外一个域的文档属性。
同源策略限制了不同源之间的交互。

如果两个URL的协议域名端口都相同,则表示它们同源

注意:
同源策略限制的不同源之间的交互主要针对的是js中的XMLHttpRequest等请求,下面这些情况是完全不受同源策略限制的:
a)在浏览器中,<script>、<img>、<iframe>、<link>等标签都可以加载跨域资源,而不受同源限制,但浏览器限制了Javascript不能读写加载的内容。。
b)页面中的链接,重定向以及表单提交是不会受到同源策略限制的。

解决跨域问题的几种方式

1. JSONP跨域访问

Jsonp(JSON with Padding) 是 json 的一种”使用模式”,可以让网页从别的域名(网站)那获取资料,即跨域读取数据。

JSONP也是开发中常见到的内容,在jquery中就有封装,通过ajax请求多带上一个jsonp的参数即可。它的实现原理其实跟ajax没有多少关系。

实现:动态的创建<script>标签,跨域的地址+需要传送的数据+回调函数<script>标签的 src 地址,执行完之后再动态的删除之前创建的<script>.

回调函数是当响应到来时要放在当前页面被调用的函数。
数据是传入回调函数中的json数据

原理:
1)首先在客户端注册一个回调函数, 然后把回调函数的名字传给服务器;
2)服务器调用这个函数并且将JSON 数据形式作为参数传递,返回给客户端,完成回调。
在页面中,返回的JSON作为参数传入回调函数中,我们通过回调函数来来操作数据。

动态实现JSONP请求
// a.html

<script>    function jsonCallback(data) {        alert(data.message);    }    function addScriptTag(src){        var script = document.createElement('script');        script.src = src;        document.body.appendChild(script);    }    window.onload = function(){        addScriptTag("http://ajax.googleapis.com/ajax/services/search/web?v=1.0&q=apple&callback=jsonCallback");    }</script>

JQuery中$.ajax的跨域实现:

 $.ajax({      async : true,      url : "https://api.douban.com/v2/book/search",      type : "GET",      dataType : "jsonp", // 返回的数据类型,设置为JSONP方式      jsonp : 'callback', //指定一个查询参数名称来覆盖默认的 jsonp 回调参数名 callback      jsonpCallback: 'handleResponse', //设置回调函数名      data : {               q : "javascript",                count : 1              },       success: function(response, status, xhr){        console.log('状态为:' + status + ',状态是:' + xhr.statusText);        console.log(response);      }});

JQuery中$.getJSON的跨域实现:

<script type="text/javascript">      $.getJSON("http://crossdomain.com/services.php?callback=?",      function(result) {          for(var i in result) {              alert(i+":"+result[i]);//循环输出a:1,b:2,etc.          }      });  </script>

缺点:
只能用 get 方式请求,因为是使用 src 来传送数据;
src 对请求的地址没有限制,会出现安全性的问题;
数据中出现中文需要编码。因为通过 url 传参数。

2. document.domain + iframe

适用于:主域相同而子域不同的情况。
实现:
1)在 http://www.a.com/a.html 和 http://script.a.com/b.html 两个文件中分别加上document.domain = ‘a.com’
2)在 a.html 文件中创建一个 iframe,去控制 iframe 的contentDocument。
//在www.a.com/a.html中

document.domain = 'a.com';var ifr = document.createElement('iframe');ifr.src = 'http://script.a.com/b.html';ifr.style.display = 'none';document.body.appendChild(ifr);ifr.onload = function(){    var doc = ifr.contentDocument || ifr.contentWindow.document;    // 在这里操纵b.html(doc)};

//在 script.a.com/b.html 中

document.domain = 'a.com';

注意:
domain只能设置为主域名,不可以在b.a.com中将domain设置为c.a.com。
缺点:
1)安全性。当一个站点(b.a.com)被攻击后,另一个站点(c.a.com)会引起安全漏洞。
2)如果一个页面中引入多个iframe,要想能够操作所有iframe,必须都得设置相同domain。

3.动态创建script

script 标签不受同源策略的限制。因此 ,通过创建script节点的方法来实现完全跨域的通信。

function loadScript(url, func) {      var head = document.head || document.getElementByTagName('head')[0];      var js = document.createElement('script');      js.src = url;//判断script节点加载完毕      js.onload = js.onreadystatechange = function(){          if(!this.readyState || this.readyState=='loaded' || this.readyState=='complete'){          func();// callback在此处执行          js.onload = js.onreadystatechange = null;    }  };      head.insertBefore(script, 0);}

ie只能通过script的readystatechange属性,其它浏览器是script的load事件。

4. location.hash + iframe

原理:利用location.hash来进行传值。

例如,http://a.com#helloword 中的 ‘#helloworld’ 就是 location.hash,改变hash并不会导致页面刷新,所以可以利用hash值来进行数据传递,当然数据容量是有限的。

假设域名a.com下的文件cs1.html要和cnblogs.com域名下的cs2.html传递信息:
1) cs1.html首先自动创建一个隐藏的iframe,iframe的src指向cnblogs.com域名下的cs2.html页面;
2) cs2.html响应请求后再将通过修改cs1.html的hash值来传递数据;
3) 同时在cs1.html上加一个定时器,隔一段时间来判断location.hash的值有没有变化,一旦有变化则获取获取hash值。

注:由于两个页面不在同一个域下,IE、Chrome不允许修改parent.location.hash的值,所以要借助于a.com域名下的一个代理iframe

a.com下的文件cs1.html文件:

function startRequest(){    var ifr = document.createElement('iframe');    ifr.style.display = 'none';    ifr.src = 'http://www.cnblogs.com/lab/cscript/cs2.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);

cnblogs.com域名下的cs2.html:

//模拟一个简单的参数处理操作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,        // 所以要利用一个中间的cnblogs域下的代理iframe        var ifrproxy = document.createElement('iframe');        ifrproxy.style.display = 'none';        ifrproxy.src = 'http://a.com/test/cscript/cs3.html#somedata';    // 注意该文件在"a.com"域下        document.body.appendChild(ifrproxy);    }}

5. window.name + iframe

window对象name属性的特征:
在一个窗口(window)的生命周期内,窗口载入的所有的页面都是共享一个window.name的,每个页面对window.name都有读写的权限,window.name是持久存在一个窗口载入过的所有页面中的,并不会因新页面的载入而进行重置。

并且可以支持非常长的 name 值(2MB)。

实现:
1)在应用页面(a.com/app.html)中创建一个iframe,把其src指向数据页面(b.com/data.html)
2)在应用页面(a.com/app.html)中,监听iframe的onload事件,在此事件中设置这个iframe的src指向本地域的代理文件(代理文件和应用页面在同一域下,所以可以相互通信)
3)获取数据以后销毁这个iframe,释放内存;这也保证了安全(不被其他域frame js访问)
//a.com/app.html

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

//data.html

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

销毁

<script>    iframe.contentWindow.document.write('');    iframe.contentWindow.close();    document.body.removeChild(iframe);</script>

参考:
window.name实现的跨域数据传输
使用 window.name 解决跨域问题
利用window.name+iframe跨域获取数据详解

6. HTML5 的 window.postMessage 方法

window.postMessage(message,targetOrigin) 方法是 html5 新引进的特性,可以使用它来向其它的window对象发送消息,无论这个window对象是属于同源或不同源。

otherWindow.postMessage(message,targetOrigin)
otherWindow——指要接收消息的那一个window对象。
message——要发送的数据。string类型
targetOrigin——用于限制接收消息的window对象,“*”表示不作限制

otherWindow 通过监听自身的 message 事件来获取传过来的消息,消息内容储存在该事件对象的data属性中。

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

<iframe id="ifr" src="b.com/index.html"></iframe><script>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 come from a.com!', targetOrigin);};</script>

b.com/index.html中的代码:

<script>window.addEventListener('message', function(event){     // 通过origin属性判断消息来源地址     if (event.origin == 'http://a.com') {         alert(event.data);    // 弹出"I come from a.com!"             }    }, false);</script>