关于跨域的一些知识

    由于公司最近要做一个跨域的项目,所以研究了一个跨域,并实践了其中的几中方法。这理总结一下。

 ==========================跨域-知识===========================

什么是跨域?

    首先什么是跨域,简单地理解就是因为JavaScript同源策略的限制。

 

什么是同源策略?

    在客户端编程语言中,如JavaScript和ActionScript,同源策略是一个很重要的安全理念,它在保证数据的安全性方面有着重要的意义。同源策略规定跨域之间的脚本是隔离的,一个域的脚本不能访问和操作另外一个域的绝大部分属性和方法。那么什么叫相同域,什么叫不同的域呢?当两个域具有相同的协议(如http), 相同的端口(如80),相同的host(如www.example.org),那么我们就可以认为它们是相同的域。比如 http://www.example.org/index.html和http://www.example.org/sub/index.html是同域,而http://www.example.org, https://www.example.org, http://www.example.org:8080, http://sub.example.org中的任何两个都将构成跨域。同源策略还应该对一些特殊情况做处理,比如限制file协议下脚本的访问权限。本地的HTML文件在浏览器中是通过file协议打开的,如果脚本能通过file协议访问到硬盘上其它任意文件,就会出现安全隐患,目前IE8还有这样的隐患。
    受到同源策略的影响,跨域资源共享就会受到制约。但是随着人们的实践和浏览器的进步,目前在跨域请求的技巧上,有很多宝贵经验的沉淀和积累。这里我把跨域资源共享分成两种,一种是单向的数据请求,还有一种是双向的消息通信。

    详细情况请看下表:

 

URL

说明

是否允许通信

http://www.a.com/a.js
http://www.a.com/b.js

同一域名下

允许

http://www.a.com/lab/a.js
http://www.a.com/script/b.js

同一域名下不同文件夹

允许

http://www.a.com:8000/a.js
http://www.a.com/b.js

同一域名,不同端口

不允许

http://www.a.com/a.js
https://www.a.com/b.js

同一域名,不同协议

不允许

http://www.a.com/a.js
http://70.32.92.74/b.js

域名和域名对应ip

不允许

http://www.a.com/a.js
http://script.a.com/b.js

主域相同,子域不同

不允许

http://www.a.com/a.js
http://a.com/b.js

同一域名,不同二级域名(同上)

不允许(cookie这种情况下也不允许访问)

http://www.cnblogs.com/a.js
http://www.a.com/b.js

不同域名

不允许

 

 

一、单向跨域
   JSONP
    JSONP (JSON with Padding)是一个简单高效的跨域方式,HTML中的script标签可以加载并执行其他域的JavaScript,于是我们可以通过script标记来动态加载其他域的资源。例如我要从域A的页面pageA加载域B的数据,那么在域B的页面pageB中我以JavaScript的形式声明pageA 需要的数据,然后在pageA中用script标签把pageB加载进来,那么pageB中的脚本就会得以执行。JSONP在此基础上加入了回调函数,pageB加载完之后会执行pageA中定义的函数,所需要的数据会以参数的形式传递给该函数。JSONP易于实现,但是也会存在一些安全隐患,如果第三方的脚本随意地执行,那么它就可以篡改页面内容,截获敏感数据。但是在受信任的双方传递数据,JSONP是非常合适的选择。

    Flash URLLoader
    Flash有自己的一套安全策略,服务器可以通过crossdomain.xml文件来声明能被哪些域的SWF文件访问,SWF也可以通过API来确定自身能被哪些域的SWF加载。当跨域访问资源时,例如从域www.a.com请求域www.b.com上的数据,我们可以借助Flash来发送 HTTP请求。首先,修改域www.b.com上的crossdomain.xml(一般存放在根目录,如果没有需要手动创建) ,把www.a.com加入到白名单。其次,通过Flash URLLoader发送HTTP请求,最后,通过Flash API把响应结果传递给JavaScript。Flash URLLoader是一种很普遍的跨域解决方案,不过需要支持iOS的话,这个方案就无能为力了。

     Access Control
     Access Control是比较超越的跨域方式,目前只在很少的浏览器中得以支持,这些浏览器可以发送一个跨域的HTTP请求(Firefox, Google Chrome等通过XMLHTTPRequest实现,IE8下通过XDomainRequest实现),请求的响应必须包含一个Access- Control-Allow-Origin的HTTP响应头,该响应头声明了请求域的可访问权限。例如www.a.com对www.b.com下的 asset.php发送了一个跨域的HTTP请求,那么asset.php必须加入如下的响应头:header("Access-Control-Allow-Origin: http://www.a.com%22);%7f/

    window.name
    window对象的name属性是一个很特别的属性,当该window的location变化,然后重新加载,它的name属性可以依然保持不变。那么我们可以在页面A中用iframe加载其他域的页面B,而页面B中用JavaScript把需要传递的数据赋值给 window.name,iframe加载完成之后,页面A修改iframe的地址,将其变成同域的一个地址,然后就可以读出window.name的值了。这个方式非常适合单向的数据请求,而且协议简单、安全。不会像JSONP那样不做限制地执行外部脚本。

    server proxy
    在数据提供方没有提供对JSONP协议或者window.name协议的支持,也没有对其它域开放访问权限时,我们可以通过server proxy的方式来抓取数据。例如当www.a.com域下的页面需要请求www.b.com下的资源文件asset.txt时,直接发送一个指向 www.b.com/asset.txt的ajax请求肯定是会被浏览器阻止。这时,我们在www.a.com下配一个代理,然后把ajax请求绑定到这个代理路径下,例如www.a.com/proxy/, 然后这个代理发送HTTP请求访问http://www.b.com/下的asset.txt,跨域的HTTP请求是在服务器端进行的,客户端并没有产生跨域的ajax请求。这个跨域方式不需要和目标资源签订协议,带有侵略性,另外需要注意的是实践中应该对这个代理实施一定程度的保护,比如限制他人使用或者使用频率。

 

二、双向跨域
    document.domain
    通过修改document的domain属性,我们可以在域和子域或者不同的子域之间通信。同域策略认为域和子域隶属于不同的域,比如 www.a.com和sub.a.com是不同的域,这时,我们无法在www.a.com下的页面中调用sub.a.com中定义的JavaScript 方法。但是当我们把它们document的domain属性都修改为a.com,浏览器就会认为它们处于同一个域下,那么我们就可以互相调用对方的 method来通信了。

    FIM – Fragment Identitier Messaging
    不同的域之间,JavaScript只能做很有限的访问和操作,其实我们利用这些有限的访问权限就可以达到跨域通信的目的了。FIM (Fragment Identitier Messaging)就是在这个大前提下被发明的。父窗口可以对iframe进行URL读写,iframe也可以读写父窗口的URL,URL有一部分被称为frag,就是#号及其后面的字符,它一般用于浏览器锚点定位,Server端并不关心这部分,应该说HTTP请求过程中不会携带frag,所以这部分的修改不会产生HTTP请求,但是会产生浏览器历史记录。FIM的原理就是改变URL的frag部分来进行双向通信。每个window通过改变其他 window的location来发送消息,并通过监听自己的URL的变化来接收消息。这个方式的通信会造成一些不必要的浏览器历史记录,而且有些浏览器不支持onhashchange事件,需要轮询来获知URL的改变,最后,URL在浏览器下有长度限制,这个制约了每次传送的数据量。

    Flash LocalConnection
     页面上的双向通信也可以通过Flash来解决,Flash API中有LocalConnection这个类,该类允许两个SWF之间通过进程通信,这时SWF可以播放在独立的Flash Player或者AIR中,也可以嵌在HTML页面或者是PDF中。遵循这个通信原则,我们可以在不同域的HTML页面各自嵌套一个SWF来达到相互传递数据的目的了。SWF通过LocalConnection交换数据是很快的,但是每次的数据量有40kb的大小限制。用这种方式来跨域通信过于复杂,而且需要了2个SWF文件,实用性不强。

    window.postMessage
    window.postMessage是HTML5定义的一个很新的方法,这个方法可以很方便地跨window通信。由于它是一个很新的方法,所以在很旧和比较旧的浏览器中都无法使用。

    Cross Frame
    Cross Frame是FIM的一个变种,它借助了一个空白的iframe,不会产生多余的浏览器历史记录,也不需要轮询URL的改变,在可用性和性能上都做了很大的改观。它的基本原理大致是这样的,假设在域www.a.com上有页面A.html和一个空白代理页面proxyA.html, 另一个域www.b.com上有个页面B.html和一个空白代理页面proxyB.html,A.html需要向B.html中发送消息时,页面会创建一个隐藏的iframe, iframe的src指向proxyB.html并把message作为URL frag,由于B.html和proxyB.html是同域,所以在iframe加载完成之后,B.html可以获得iframe的URL,然后解析出 message,并移除该iframe。当B.html需要向A.html发送消息时,原理一样。Cross Frame是很好的双向通信方式,而且安全高效,但是它在Opera中无法使用,不过在Opera下面我们可以使用更简单的 window.postMessage来代替。

 

 

本文参考文章:

    http://www.cnblogs.com/rainman/archive/2011/02/20/1959325.html

    http://www.cnblogs.com/cat3/archive/2011/06/15/2081559.html

 
 
  =========================跨域-使用JSONP==========================

   此为单向跨域。实现方法是域A向域B请求数据,域B生成JSONP的数据给A。我们使用Jquery实现通信。

 

    实现步骤:

    1、域A前端调用页面

<% @ Page Language = " C# "  AutoEventWireup = " true "  CodeBehind = " Default.aspx.cs "  Inherits = " WebApplication1._Default "   %>
<! DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" >

< html  xmlns ="http://www.w3.org/1999/xhtml"   >
< head  runat ="server" >
     < title >无标题页 </ title >
     < script  src ="./js/jquery.1.3.2.js"  type ="text/javascript" ></ script >
     < script  type ="text/javascript" >
    
function  getData()
    {
        
var  url  =   " http://b.5173.com/Default.aspx " ;  
        $.ajax({  
                url:url,  
                dataType:
" jsonp " ,  
                success: 
function (json){  
                             alert(json.msg);
                          },  
                error: 
function (){
                         alert(
" error " );
                         }  
               })  
    }
    
</ script >
</ head >
< body >
     < form  id ="form1"  runat ="server" >
     < input  type ="button"  value ="跨域调用"  onclick ="getData();" />
     </ form >
</ body >
</ html >
 

 

    2、域B主要是生成Jsonp,所以只有后台代码。

using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;

namespace WebApplication2
{
     public  partial  class _Default : System.Web.UI.Page
    {
         protected  void Page_Load( object sender, EventArgs e)
        {
            Response.Charset =  " utf-8 ";
            Response.ContentType =  " text/javascript ";
            

             string callback = Request.QueryString[ " callback "];
             string json =  " {'state':'0','msg':'hello world!'} ";
             string result =  string.Format( " {0}({1}) ",callback,json);
            
            
            Response.Write(result);
            Response.Flush();
            Response.End();
            
            
        }
    }
}
 

 

 

    调用结果: 

 

关于跨域的一些知识

 

 

 

参考文章:http://www.cnblogs.com/hb_cattle/archive/2011/10/27/1713659.html

 


=========================== 跨域-使用js文件==============================

   此种方法与Jsonp方法类似。是呼叫js文件。通过文件传值。方法如下。

 

1、在B域(B.com)中建立一个Js文件 JsCreateJson.js。此js返回一个json。代码如下。

{ symbol: 'IBM', price: 91.42 }

 

2、在A域(A.com)使用CrossByReferenceJs.aspx动态调用此文件。代码如下。

<% @ Page Language = " C# "  AutoEventWireup = " true "  CodeBehind = " CrossByReferenceJs.aspx.cs "
    Inherits
= " IframeTest.CrossByReferenceJs "   %>

<! DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd%22 >
< html  xmlns ="http://www.w3.org/1999/xhtml%22>
<head runat="
server" >
     < title ></ title >
     < script  type ="text/javascript" >
        
function  showPrice(data) {
            alert(
" Symbol:  "   +  data.symbol  +   " , Price:  "   +  data.price);
        }

        
var  JSONP  =  document.createElement( " script " );
        
// FF:onload IE:onreadystatechange
        JSONP.onload  =  JSONP.onreadystatechange  =   function  () {
            
// onreadystatechange,仅IE
             if  ( ! this .readyState  ||   this .readyState  ===   " loaded "   ||   this .readyState  ===   " complete " ) {
                JSONP.onload 
=  JSONP.onreadystatechange  =   null // 请内存,防止IE memory leaks
            }
        }
        JSONP.type 
=   " text/javascript " ;
        JSONP.src 
=   " http://b.com/JsCreateJson.js%22";
        //在head之后添加js文件
        document.getElementsByTagName(
" head " )[0].appendChild(JSONP);
    </script>
</head>
<body>
    <form id=
" form1 "  runat= " server " >
    <div id=
" demo " >
        <%--显示调用结果--%>
        <input id=
" Button1 "  type= " button "  value= " button "  onclick= " showPrice() "  />
    </div>
    </form>
</body>
</html>
 

 

 


============================跨域-使用window.name========================

    实现原理请看“ 跨域-知识”-window.name节。实现方法如下。

    1、在A域的CrossByWindowName.aspx页面中创建一个iframe,把其src指向B页域的CrossByWindowName.aspx页面。通过CrossByWindowName.aspx中的按钮得到通过window.name传回的值。代码如下。

<% @ Page Language = " C# "  AutoEventWireup = " true "  CodeBehind = " CrossByWindowName.aspx.cs "
    Inherits
= " IframeTest.CrossByWindowName "   %>

<! DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" >
< html  xmlns ="http://www.w3.org/1999/xhtml" >
< head  id ="Head1"  runat ="server" >
     < title ></ title >
</ head >
< body >
     < div  id ="cnt" >
         < iframe  id ="ifr"  src ="http://oatest.mic.com.tw/gsc/CrossByWindowName.aspx"  width ="100%"
            height
="500px" ></ iframe >
     </ div >
     <% -- 通过此按钮获得Window.Name值,实际得到是在iframe的onload事件中得到。
    主要是当点击后,iframe会跳转到本地页面,这样就在同一个域中,这时iframe的load完成后,就可以通过window.name得到值
-- %>
     < input  id ="btnGetWindowName"  type ="button"  value ="GetWindowName"  onclick ="GetWindowName()"   />
</ body >
</ html >
< script  type ='text/javascript' >
    
var  state  =   0 ;
    
var  iframe  =  document.getElementById( ' ifr ' );
    
// 给iframe设置onload事件。通过onload事件
     if  (iframe.attachEvent) {
        iframe.attachEvent(
" onload " function  () {
            
if  (state  ==   1 ) {
                
var  data  =  iframe.contentWindow.name;     //  读取数据
                alert(data);
            }
        });
    } 
else  {
        iframe.onload 
=  IframLoadFn;
    }

    
// onload事件
     var  IframLoadFn  =   function  () {
        
if  (state  ==   1 ) {
            
var  data  =  iframe.contentWindow.name;     //  读取数据
            alert(data);
        } 
else   if  (state  ==   0 ) {
            state 
=   1 ;
            iframe.contentWindow.location 
=   " Proxy.htm " ;     //  设置的代理文件,此文件不一定要真实存在
        }
    };

    
// state=1 时,表示取数据。0表示
     var  GetWindowName  =   function  () {
        state 
=   1 ;
        iframe.contentWindow.location 
=   " Proxy.htm " ;     //  设置的代理文件,此文件不一定要真实存在
    }
</ script >
 

 

    2、在B域的CrossByWindowName.aspx页面中通过一个按钮及输入框给window.name赋值。

<% @ Page Language = " C# "  AutoEventWireup = " true "  CodeBehind = " CrossByWindowName.aspx.cs "  Inherits = " ExtNetStudy.CrossByWindowName "   %>

<! DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" >
< html  xmlns ="http://www.w3.org/1999/xhtml" >
< head  runat ="server" >
     < title ></ title >
     < script  type ="text/javascript" >
        
var  WindowClick  =   function  () {
            window.name 
=  document.getElementById( " txtCookie " ).value;
            alert(window.name);
        }
    
</ script >
</ head >
< body >
     < form  id ="form1"  runat ="server" >
     < div >
        Winddow.Name:
         < input  id ="txtCookie"  type ="text"  value ="Window.name Value"   />
         < input  id ="btnWindow"  type ="button"  value ="SetWindow"  onclick ="WindowClick()"   />
     </ div >
     </ form >
</ body >
</ html >
 

 

 

===========================跨域-使用Proxy page或Cross Frame==========================

  原理请看“跨域-知识”Cross Frame节。 主要是在B域中在放一个iframe,这iframe在指向A域中页面,通过此页面进行传值。 

 

A域中Page.aspx

B域中B.aspx

A域中proxy.aspx.iframe为隐藏的

 

 

    实现方法如下。

   1、A域(a.com)中建立两个页面。

       CrossByProxyPage.aspx,主页面,放入一个iframe,用于调用B域中页面。

       CrossProxy.aspx,代码理页,用于传值。

 

CrossByProxyPage.aspx代码如下。

<% @ Page Language = " C# "  AutoEventWireup = " true "  CodeBehind = " CrossByProxyPage.aspx.cs "
    Inherits
= " IframeTest.CrossByProxyPage "   %>

<! DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" >
< html  xmlns ="http://www.w3.org/1999/xhtml" >
< head  runat ="server" >
     < title ></ title >
     < script  type ="text/javascript" >
        
// 代理页面通过此方法显示信息
         function  ShowParameter(parameterString) {
            
if  (parameterString.length  >   0 ) {
                alert(
" TopParent Show:  "   +  parameterString);
            }
        }
    
</ script >
</ head >
< body >
     < form  id ="form1"  runat ="server" >
    Parent Page Call Child Page
     < div >
         < iframe  id ="ifr"  src ="http://B.com/CrossByProxyPage.aspx"  width ="100%"
            height
="500px" ></ iframe >
     </ div >
     </ form >
</ body >
</ html >
 

 

    CrossProxy.aspx页面代码如下。

<% @ Page Language = " C# "  AutoEventWireup = " true "  CodeBehind = " CrossProxy.aspx.cs "  Inherits = " IframeTest.CrossProxy "   %>

<! DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" >
< html  xmlns ="http://www.w3.org/1999/xhtml" >
< head  runat ="server" >
     < title ></ title >
     < script  type ="text/javascript" >
        
// 得到通过Url传过来的值
         var  praIndex  =  location.href.indexOf( " ? " );
        
var  parameterString  =  praIndex  >   0   ?  location.href.substring(praIndex  +   1 , location.href.length) :  "" ;
        
var  topParent  =  parent.parent;
        
//调用 主页面js方法显示信息
         var  topParentfunc  =  parent.parent.ShowParameter;
        
if  (topParentfunc) {
            topParentfunc(parameterString);
        }
    
</ script >
</ head >
< body >
     < form  id ="form1"  runat ="server" >
     < div >
      This is Proxy Page!
     </ div >
     </ form >
</ body >
</ html >
 

 

    2、B域(b.com)中的被调用页面CrossByProxyPage.aspx。此页中放入一个iframe,用于调用A域中的代理页面。

<% @ Page Language = " C# "  AutoEventWireup = " true "  CodeBehind = " CrossByProxyPage.aspx.cs "
    Inherits
= " ExtNetStudy.CrossByProxyPage "   %>

<! DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" >
< html  xmlns ="http://www.w3.org/1999/xhtml" >
< head  runat ="server" >
     < title ></ title >
     < script  type ="text/javascript" >
        
var  SetParameterClick  =   function  () {
            
var  iframeObj  =  document.getElementById( " iframProxy " );
            
if  (iframeObj) {
                
// 通过URL传递参数,你也可以使用hash(#)传送参数
                 var  iframeSrc  =  iframeObj.src  +   " ?pra= "   +  document.getElementById( " ibnParameter " ).value;
                alert(
" child Url:  "   +  iframeSrc);
                iframeObj.src 
=  iframeSrc;
            }
            
else  {
                alert(
" Get Proxy page Faile " );
            }

        }
    
</ script >
</ head >
< body >
     < form  id ="form1"  runat ="server" >
    Child page Call Proxy page
     < div >
         < iframe  id ="iframProxy"  src ="http://A.com/CrossProxy.aspx"  width ="100%"
            height
="100px" ></ iframe >
     </ div >
     <% -- 设置要传递的参数 -- %>
     < input  id ="ibnParameter"  type ="text"  value ="TestParameter"   />
     < input  id ="btnSetParameter"  type ="button"  value ="Set Paraameter"  onclick ="SetParameterClick()"   />
     </ form >
</ body >
</ html >
 

 

说明:

    此种方法也可以变通一下,如果B.com页面不需要长时间显示,可以直接可以B.com页跳转到A.com中的页面。如下代码。

 Response.Redirect( " http://A.com/CrossProxy.aspx?id=001&data=1111 ");

 


 


你可能感兴趣的:(跨域)