跨域方法总结

haoran 7年前
   <p>最近面试问的挺多的一个问题,就是JavaScript的跨域问题。在这里,对跨域的一些方法做个总结。由于浏览器的同源策略,不同域名、不同端口、不同协议都会构成跨域;但在实际的业务中,很多场景需要进行跨域传递信息,这样就催生出多种跨域方法。</p>    <h3><strong>1. 具备src的标签</strong></h3>    <ul>     <li> <p>原理:所有具有 src 属性的HTML标签都是可以跨域的</p> </li>    </ul>    <p>在浏览器中, <script> 、 <img> 、 <iframe> 和 <link> 这几个标签是可以加载跨域(非同源)的资源的,并且加载的方式其实相当于一次普通的GET请求,唯一不同的是,为了安全起见,浏览器不允许这种方式下对加载到的资源的读写操作,而只能使用标签本身应当具备的能力(比如脚本执行、样式应用等等)。</p>    <h3><strong>2. JSONP跨域</strong></h3>    <ul>     <li> <p>原理: <script> 是可以跨域的,而且在跨域脚本中可以直接回调当前脚本的函数</p> </li>    </ul>    <p>script标签是可以加载异域的JavaScript并执行的,通过预先设定好的callback函数来实现和母页面的交互。它有一个大名,叫做JSONP跨域,JSONP是JSON with Padding的略称。它是一个非官方的协议,明明是加载script,为啥和JSON扯上关系呢?原来就是这个callback函数,对它的使用有一个典型的方式,就是通过JSON来传参,即将JSON数据填充进回调函数,这就是JSONP的JSON+Padding的含义。JSONP只支持GET请求。</p>    <p>前端代码:</p>    <pre>  <script type="text/javascript">   function dosomething(jsondata){   //处理获得的json数据   }  </script>  <script src="http://haorooms.com/data.php?callback=dosomething"></script>  </pre>    <p>后台代码:</p>    <pre>  <?php  $callback = $_GET['callback'];//得到回调函数名  $data = array('a','b','c');//要返回的数据  echo $callback.'('.json_encode($data).')';//输出  ?>  </pre>    <h3><strong>3. 跨域资源共享(CORS)</strong></h3>    <ul>     <li> <p>原理:服务器设置Access-Control-Allow-Origin HTTP响应头之后,浏览器将会允许跨域请求</p> </li>    </ul>    <p>CORS是HTML5标准提出的跨域资源共享(Cross Origin Resource Share),支持GET、POST等所有HTTP请求。CORS需要服务器端设置 Access-Control-Allow-Origin 头,否则浏览器会因为安全策略拦截返回的信息。</p>    <pre>  Access-Control-Allow-Origin: *              # 允许所有域名访问,或者  Access-Control-Allow-Origin: http://a.com   # 只允许所有域名访问  </pre>    <p>CORS又分为简单跨域和非简单跨域请求,有关CORS的详细介绍请看 阮一峰 的 跨域资源共享 CORS 详解 ,里面讲解的非常详细。</p>    <h3><strong>4. document.domain</strong></h3>    <ul>     <li> <p>原理:相同主域名不同子域名下的页面,可以设置document.domain让它们同域</p> </li>    </ul>    <p>我们只需要在跨域的两个页面中设置document.domain就可以了。修改document.domain的方法只适用于不同子域的框架间的交互,要载入iframe页面。</p>    <p>例如:1. 在页面 http://a.example.com/a.html 设置document.domain</p>    <pre>  <iframe id = "iframe" src="http://b.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>  </pre>    <p>2、在页面http:// b.example.com/b.html 中设置document.domain</p>    <pre>  <script type="text/javascript">   document.domain = 'example.com';//在iframe载入这个页面也设置document.domain,使之与主页面的document.domain相同  </script>  </pre>    <h3><strong>5. window.name</strong></h3>    <ul>     <li> <p>原理:window对象有个name属性,该属性有个特征:即在一个窗口(window)的生命周期内,窗口载入的所有的页面都是共享一个window.name的,每个页面对window.name都有读写的权限,window.name是持久存在一个窗口载入过的所有页面中的。</p> </li>    </ul>    <p>这里有三个页面:</p>    <ul>     <li> <p>sever.com/a.html 数据存放页面</p> </li>     <li> <p>agent.com/b.html 数据获取页面</p> </li>     <li> <p>agent.com/c.html 空页面,做代理使用</p> </li>    </ul>    <p>a.html中,设定 window.name 作为需要传递的值</p>    <pre>  <script>   window.name = 'I was there!';   alert(window.name);  </script>  </pre>    <p>b.html中,当iframe加载后将iframe的 src 指向同域的 c.html ,这样就可以利用 iframe.contentWindow.name 获取要传递的值了</p>    <pre>  <body>    <script type="text/javascript">   iframe = document.createElement('iframe');   iframe.style.display = 'none';   var state = 0;   iframe.onload = function() {   if(state === 1) {   var data = JSON.parse(iframe.contentWindow.name);   alert(data);   iframe.contentWindow.document.write('');   iframe.contentWindow.close();   document.body.removeChild(iframe);   } else if(state === 0) {   state = 1;   iframe.contentWindow.location = 'http://agent.com/c.html';   }   };   iframe.src = 'http://sever.com/a.html';   document.body.appendChild(iframe);   </script>  </body>  </pre>    <p>成功获取跨域数据,效果如下:</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/ace9d5c3c5bbb8b4d45c50715bc8009f.png"></p>    <h3><strong>6. window.postMesage</strong></h3>    <ul>     <li> <p>原理: HTML5新增的postMessage方法,通过postMessage来传递信息,对方可以通过监听message事件来监听信息。可跨主域名及双向跨域。</p> </li>    </ul>    <p>这里有两个页面:</p>    <ol>     <li> <p>agent.com/index.html</p> </li>     <li> <p>server.com/remote.html</p> </li>    </ol>    <p>本地代码index.html</p>    <pre>  <body>        <iframe id="proxy" src="http://server.com/remote.html" onload = "postMsg()" style="display: none" ></iframe>        <script type="text/javascript">    var obj = {    msg: 'hello world'    }    function postMsg (){    var iframe = document.getElementById('proxy');    var win = iframe.contentWindow;    win.postMessage(obj,'http://server.com');    }    </script>    </body>  </pre>    <p>postMessage 的使用方法: otherWindow.postMessage(message, targetOrigin);</p>    <ul>     <li> <p>otherWindow: 指目标窗口,也就是给哪个window发消息,是 window.frames 属性的成员或者由 window.open 方法创建的窗口</p> </li>     <li> <p>message: 是要发送的消息,类型为 String、Object (IE8、9 不支持)</p> </li>     <li> <p>targetOrigin: 是限定消息接收范围,不限制请使用 ‘*’</p> </li>    </ul>    <p>server.com上remote.html,监听 message 事件,并检查来源是否是要通信的域。</p>    <pre>  <head>      <title></title>      <script type="text/javascript">          window.onmessage = function(e){              if(e.origin !== 'http://localhost:8088') return;              alert(e.data.msg+" from "+e.origin);          }      </script>  </head>  </pre>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/a0b402a1206ced64f1ba64dabbafd902.png"></p>    <h3><strong>7. location.hash</strong></h3>    <p>原理:</p>    <ul>     <li> <p>这个办法比较绕,但是可以解决完全跨域情况下的脚步置换问题。原理是利用location.hash来进行传值。www.a.com下的a.html想和www.b.com下的b.html通信(在a.html中动态创建一个b.html的iframe来发送请求)</p> </li>     <li> <p>但是由于“同源策略”的限制他们无法进行交流(b.html无法返回数据),于是就找个中间人:www.a.com下的c.html(注意是www.a.com下的)。</p> </li>     <li> <p>b.html将数据传给c.html(b.html中创建c.html的iframe),由于c.html和a.html同源,于是可通过c.html将返回的数据传回给a.html,从而达到跨域的效果。</p> </li>    </ul>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/b7529b1a5e5f63f2ec4b26e5220071ca.jpg"></p>    <p>a.html代码如下:</p>    <pre>  <script>  function startRequest(){    var ifr = document.createElement('iframe');    ifr.style.display = 'none';    ifr.src = 'http://www.b.com/b.html#sayHi'; //传递的location.hash    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);   window.onload = startRequest;  </script>  </pre>    <p>b.html代码如下:</p>    <pre>  <script>  function checkHash(){   var data = '';   //模拟一个简单的参数处理操作   switch(location.hash){   case '#sayHello': data = 'HelloWorld';break;   case '#sayHi': data = 'HiWorld';break;   default: break;   }   data && callBack('#'+data);  }  function callBack(hash){   // ie、chrome的安全机制无法修改parent.location.hash,所以要利用一个中间的www.a.com域下的代理iframe   var proxy = document.createElement('iframe');   proxy.style.display = 'none';   proxy.src = 'http://localhost:8088/proxy.html'+hash; // 注意该文件在"www.a.com"域下   document.body.appendChild(proxy);  }  window.onload = checkHash;  </script>  </pre>    <p>由于两个页面不在同一个域下,IE、Chrome不允许修改parent.location.hash的值,所以要借助于a.com域名下的一个代理iframe,这里有一个a.com下的代理文件c.html。Firefox可以修改。</p>    <p>c.html代码如下:</p>    <pre>  <script>parent.parent.location.hash = self.location.hash.substring(1); </script>  </pre>    <p>直接访问a.html,a.html向b.html发送的消息为”sayHi”;b.html通过消息判断返回了”HiWorld”,并通过c.html改变了location.hash的值</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/ebc1e94ce76810d04d1e4dec5d81d154.png"></p>    <h3><strong>8. flash URLLoader</strong></h3>    <p>flash有自己的一套安全策略,服务器可以通过crossdomain.xml文件来声明能被哪些域的SWF文件访问,SWF也可以通过API来确定自身能被哪些域的SWF加载。当跨域访问资源时,例如从域baidu.com请求域google.com上的数据,我们可以借助flash来发送HTTP请求。首先,修改域google.com上的crossdomain.xml(一般存放在根目录,如果没有需要手动创建) ,把baidu.com加入到白名单。其次,通过Flash URLLoader发送HTTP请求,最后,通过Flash API把响应结果传递给JavaScript。Flash URLLoader是一种很普遍的跨域解决方案,不过需要支持iOS的话,这个方案就不可行了。</p>    <p>小结</p>    <p>总的来说,常见的跨域方法如上述。在不同的业务场景下,各有适合的跨域方式。跨域解决了一些资源共享、信息交互的难题,但是有的跨域方式可能会带来安全问题,如jsonp可导致水坑攻击,等标签会被用来进行xss或csrf攻击。所以,在应用跨域的场景,需要格外注意安全问题。</p>    <p> </p>    <p>来自:http://wps2015.org/2016/10/17/summary-of-cross-domain/</p>    <p> </p>