Javascript跨域和Ajax跨域解决方案

来源:互联网 发布:三一重工员工待遇知乎 编辑:程序博客网 时间:2024/06/05 00:48
 
ajax跨域和JS的跨域通信(Cross The Site)的几种解决方案


     最近做的一个项目中需要ajax跨域取得数据,如果是在本域中确实没有问题,但是放到二级域和其他域下浏览器直接就弹出提示框:“该页正在访问其控制范围之外的数据,这有些危险,是否继续"


1.什么引起了ajax跨域不能的问题
ajax本身实际上是通过XMLHttpRequest对象来进行数据的交互,而浏览器出于安全考虑,不允许js代码进行跨域操作,所以会警告。

2.有什么完美的解决方案么?
没有。解决方案有不少,但是只能是根据自己的实际情况来选择。

具体情况有:
一、本域和子域的相互访问: www.aa.com和book.aa.com
二、本域和其他域的相互访问: www.aa.com和www.bb.com 用 iframe
三、本域和其他域的相互访问: www.aa.com和www.bb.com 用 XMLHttpRequest访问代理
四、本域和其他域的相互访问: www.aa.com和www.bb.com 用 JS创建动态脚本


解决方法
一、如果想做到数据的交互,那么www.aa.com和book.aa.com必须由你来开发才可以。可以将book.aa.com用iframe添加到 www.aa.com的某个页面下,在www.aa.com和iframe里面都加上document.domain = "aa.com",这样就可以统一域了,可以实现跨域访问。就和平时同一个域中镶嵌iframe一样,直接调用里面的JS就可以了。(这个办法我没有尝试,不过理论可行)


二、当两个域不同时,如果想相互调用,那么同样需要两个域都是由你来开发才可以。用iframe可以实现数据的互相调用。解决方案就是用window.location对象的hash属性。hash属性就是http://domian/web/a.htm#dshakjdhsjka 里面的#dshakjdhsjka。利用JS改变hash值网页不会刷新,可以这样实现通过JS访问hash值来做到通信。不过除了IE之外其他大部分浏览器只要改变hash就会记录历史,你在前进和后退时就需要处理,非常麻烦。不过再做简单的处理时还是可以用的,具体的代码我再下面有下载。大体的过程是页面a和页面b在不同域下,b通过iframe添加到a里,a通过JS修改iframe的hash值,b里面做一个监听(因为JS只能修改hash,数据是否改变只能由b自己来判断),检测到b的hash值被修改了,得到修改的值,经过处理返回a需要的值,再来修改a的hash值(这个地方要注意,如果a 本身是那种查询页面的话比如http://domian/web/a.aspx?id=3,在b中直接parent.window.location是无法取得数据的,同样报没有权限的错误,需要a把这个传过来,所以也比较麻烦),同样a里面也要做监听,如果hash变化的话就取得返回的数据,再做相应的处理。


三、这种情形是最经常遇到的,也是用的最多的了。就是www.aa.com和www.bb.com你只能修改一个,也就是另外一个是别人的,人家告诉你你要取得数据就访问某某连接参数是什么样子的,最后返回数据是什么格式的。而你需要做的就是在你的域下新建一个网页,让服务器去别人的网站上取得数据,再返回给你。domain1下的a向同域下的GetData.aspx请求数据,GetData.aspx向domain2下的 ResponseData.aspx发送请求,ResponseData.aspx返回数据给GetData.aspx, GetData.aspx再返回给a,这样就完成了一次数据请求。GetData.aspx在其中充当了代理的作用。具体可以看下我的代码。


四、这个和上个的区别就是请求是使用<script>标签来请求的,这个要求也是两个域都是由你来开发才行。原理就是JS文件注入,在本域内的a 内生成一个JS标签,它的SRC指向请求的另外一个域的某个页面b,b返回数据即可,可以直接返回JS的代码。因为script的src属性是可以跨域的。具体看代码,这个也比较简单。

code:
http://www.live-share.com/files/300697/Cross_The_Site_Test_code.rar.html
(csdn不能粘贴附件么?)

总结
第一种情况:域和子域的问题,可以完全解决交互。
第二种情况:跨域,实现过程非常麻烦,需要两个域开发者都能控制,适用于简单交互。
第三种情况:跨域,开发者只控制一个域即可,实现过程需要增加代理取得数据,是常用的方式。
第四种情况:跨域,两个域开发者都需要控制,返回一段js代码。

PS:代码自己按照情况修改即可。

这是拿别人的参考链接,老美的文章比较多。

1. Security Considerations: Dynamic HTML
http://msdn.microsoft.com/library/default.asp?url=/workshop/author/dhtml/sec_dhtml.asp

2. About Cross-Frame Scripting and Security
http://msdn.microsoft.com/library/default.asp?url=/workshop/author/om/xframe_scripting_security.asp

3. Cross-Domain Proxy
http://ajaxpatterns.org/Cross-Domain_Proxy

4. Cross Domain XMLHttpRequest using an IFrame Proxy
http://manual.dojotoolkit.org/WikiHome/DojoDotBook/Book75

5. Back Button Support for Atlas UpdatePanels
http://www.nikhilk.net/BackButtonSupport.aspx

6. Cross-document messaging hack
http://blog.monstuff.com/archives/000304.html

7. Building Mash-ups with "Atlas"
http://atlas.asp.net/docs/Walkthroughs/DevScenarios/bridge.aspx

8. Calling web services hosted outside of your application with “Atlas”
http://blogs.msdn.com/federaldev/archive/2006/07/31/684229.aspx

http://www.federaldeveloper.com/Shared%20Documents/Presentations%20by%20Marc%

20Schweigert/CallAtlasWebServiceInDifferentProject.zip

9. AJAX Tip: Passing Messages Between iframes
http://www.25hoursaday.com/weblog/PermaLink.aspx?guid=3b03cf9d-b589-4838-806e-64efcc0a1a15

10. OSCON Cross-site Ajax Slides
http://blog.plaxo.com/archives/2006/07/oscon_crosssite.html

http://www.plaxo.com/css/api/Joseph-Smarr-Plaxo-OSCON-2006.ppt

11. OSCON 2006: Cross-site Ajax
http://www.sitepoint.com/blogs/2006/07/28/oscon-2006-cross-site-ajax/
-------------------------------------------------------------------------------------------

Javascript 跨域访问解决方案

2008-9-27 15:03:00查看学习心得

由于安全方面的考虑,Javascript被限制了跨域访问的能力,但是有时候我们希望能够做一些合理的跨域访问的事情,那么怎么办呢?

  这里分两类情况:

  一、基于同一父域的子域之间页面的访问

  参见如下3个domain域:

1、taobao.com
  2、jipiao.taobao.com
  3、promotion.taobao.com

  它们有相同的父域 taobao.com

  二、基于不同父域页面之间的访问

  参见如下3个domain域:

1、taobao.com
   2、baidu.com

  3、sina.com.cn

  它们具有不同的父域。

  解决它们之间跨域的方案:

  <!--[if !supportLists]-->① <!--[endif]-->服务器Proxy: 域A的页面JS需要访问域B下的链接获取数据,该方案在域A的服务器端建立一个Proxy程序(可能是ASP、servlet等任何服务端程序),域A的页面JS直接调用本域下的Proxy程序,proxy程序负责将请求发送给域B下的链接并获取到数据,最后再通过Proxy将数据返回给页面JS使用。

  经过的访问流程就是: 域A下JS--à域A 下Proxy---à域B下的链接

  例子:

  第一步:

  域A: http://Jipiao.taobao.com/test.htm 页面上javascript脚本

  <script type=”text/javascript”>
  Var sUrl=” http://Jipiao.taobao.com/proxy.do”; //本域下代理地址
var callback =
{
  success: function(res) { alert(res.responseText); },
  failure: function(res) { alert(‘failure’);},
  argument:{}
}
 YAHOO.util.Connect.asyncRequest(’GET’, sUrl, callback, null);
</script>

第二步:

  Proxy程序(这里假定是一个servlet):

Public class Proxy extends …….{
..doGet(……..){
HttpClient client=……;
GetMethod get=new GetMethod("www.baidu.com/xxxxx.do");//访问域B的链接
int statusCode = client.executeMethod(get);
if (statusCode != HttpStatus.SC_OK) {
  byte[] responseBody = get.getResponseBody();
String res=new String(responseBody);
Httpresponse.getWriter().write(res);//将数据返回给域A }
}
}
<!--[if !supportLists]-->② <!--[endif]-->Script标签: 域A页面http://Jipiao.taobao.com/test.htm 的head中写一个空的Script标签
<html>
<head>
 <script id=”remoteScript” type=”text/javascript” src=”” />
<head>
<body>
<script type=”text/javascript” >
 Var remoteScript=document.getElementById(‘remoteScript’);
remoteScript.src=” www.baidu.com/xxxxx.do”;//域B的链接
 alert(remote.test);//使用域B返回的JSON数据
 alert(f[0]);
</script>
</body>
</html>

  注意:这种方案要求域B返回的数据必须是合法的JSON格式或者如JS文件的格式。

  域B返回的数据格式如下:

Var remote={test:’hello’};
Var f=[‘2,1];

  对于基于同一父域的子域之间页面的访问这一类情况,还有第三种方式
③ 隐藏iframe: 即域A jipiao.taobao.com/yyyy.htm 的页面上写一个隐藏的iframe,

<html>
<head>
<head>
<body>
<script type=”text/javascript” >
Document.domain=”taobao.com”;
Var remoteHtml=document.getElementById(“remoteHtml”);
remoteHtml.src=”promotion.taobao.com/xxxx.htm”;//这里访问域B的链接
var document=remoteHtml.ContentDocument;
 …//这里就可以使用document来操作域B中页面xxx.htm的数据了
</script>
<iframe id=”remoteHtml” src=”” style=”diapay:none”/>
</body>
</html>

  这里 promotion.taobao.com/xxxx.htm 页面也需要设置document.domain="taobao.com", 这种方法才能奏效。之所以这种iframe的方法不适合不同父域之间的跨域,是因为设置document.domain只能设置为自己的父域,而不是能设置为其他域,例如:jiapiao.taobao.com只能设置document.domain=”taobao.com” ,而不是是document.domain=”baidu.com”

  优缺点比较:

  这里列举的三种方案各有优缺点:

  Proxy方案优点是可以适用用于几乎所有的跨域访问,而且只需要要一个域中进行开发,另一个域可以提供任何类型格式的数据。缺点是这种方案经过了中间Proxy,所以延迟可能稍微大一点,并且会加重本域服务器的负荷,开发工作量也稍微大一点。

  Script标签的方案可以说是非常简单的,不用几行代码就搞定了事,不过它对返回的数据格式要求有点严格,只能是Json格式数据,如果是其他格式的数据,那么这种方法就无能为力了。


  隐藏iframe方式也很简单,它可以处理任何返回的数据格式,但它只适用在具有同一个父域下的跨域请求上,并且要求其他域得配合开发,即需要设置document.domain
-----------------------------------------------------------------------------------------------------

跨域登录(一)

跨域登录是一个比较烦人的事情,往往我们需要写文章记录下来,或者探讨,或者抛砖引玉的问题,都是令人头疼的。上次简单得写了一篇关于跨域登录的文章,只讲了大体的实现过程。但是现在碰到了更大的问题,这篇文章将会介绍这个成败,并探讨、实现新方案的可行性。

跨域登录需要一张通行证,也可以称之为票据。就老衲现在知道和实验的方式一共有三种:

1、浏览器get参数;
2、session
3、cookie

每个都有特定的条件,以及需要处理的细节,也会带来一些新问题。根据经验,浏览器带参数,将会使系统开发得不像个东西,至少我是这么认为的。需要考虑对这个地址参数处理的各种策略,一开始我就否定了这个方案。在我需要改造的项目中有4个独立域名,跳转来跳转去,将会给用户造成极为不爽的体验。

session也可以解决问题,但是有一个问题无法解决。

先看看怎么用session解决问题。假设现在有a.com,b.com,现在开两个子域名: passport.a.com和passport.b.com。然后把这两个域名指向同一个站点,也就是在同一个站点的http投绑定这两个域名。

那么登录的时候,在 passport.a.com上登录成功,就可以设置一个session,那么在两个系统当中都是可以通过代理文件,访问到这个session的,这个方案确实是可行的。但是session只能保持20分钟,新问题就出来了。假设这个用户20分钟没有去操作,而打开了另外一个域名,那么这个判断就失效了。访问本域是没问题的,cookie还在那里摆着。

我比较倾向于用cookie来解决问题。上一次设计的系统,可以说极其简单。4个系统,有3个是asp.net的,还有个论坛是asp的(不用说就是动网的了)。现在就有四套登录系统。如果整体上全部改造,老衲认为成本太大了。后来四处逛网站,借鉴了Sohu的登录方式,但是只做了个体验的实现,如果全部实现了就不会现在在这里探讨这个问题了。解决方案就是javascript + iframe实现的。

本来想用纯javascript实现,然后给src的文件带参数,但是实际开发过程中,应该是我的js水平太菜,所以感觉不到想要的那种效果。后来就采用了javascript + iframe的方式来实现。是无刷新的那种哦,呵呵。

// JavaScript Document
//
<script type="text/javascript" language="javascript">
function $P(id)
{
return document.getElementById(id);
}
function HJ_Passport(domain)
{
/*private*/
var me = this;
var version = "1.1";
var author = "yurow";
var iframe = "<iframe id=/"passport_frm_1/" style=/"display:none/"></iframe>";
var uicode = "<div id=/"passport_login/"><p>请输入帐号和密码</p><span id=/"passport_login_user/">用户:<input id=/"passport_username/" type=/"text/"></span><span id=/"passport_login_pass/">密码:<input id=/"passport_password/" type=/"password/"></span><div id=/"passport_login_action/"><button onclick=/"hj_passport.OnSign();/">登陆</button><button onclick=/"hj_passport.UICode.Hide();/">取消</button></div></div>";
var _id;
var _g;
var intervalId = 0;
var sites = [["site1","www.a.com","SetLogin.aspx","SetLogout.aspx"],["site2","www.b.com","SetLogin.asp","SetLogout.asp"]];

/*public*/
this.passport = "http://passport.c.com/";
this.passport_ui_id = "passport_ui";
this.UserName = "";

/*private set or get*/
HJ_Passport.prototype
=
{
Author:author,
Version:version
}
this.UICode={
UICode:uicode
}
this.UICode.Set = function(code){
uicode
= code;
}
this.UICode.Draw = function(){
document.write(iframe
+ "<div id=/"" + me.passport_ui_id + "/"></div>");
}
this.UICode.Show = function(){
var pobj = $P(me.passport_ui_id);
if(pobj== null){
me.UICode.Draw();
me.UICode.Show();
}
else{
pobj.innerHTML
= uicode;
pobj.style.display
= "";
}
}
this.UICode.Hide = function()
{
var pobj = $P(me.passport_ui_id);
if(pobj!= null){
pobj.style.display
= "none";
}
};
this.State =
{
ID :
function(){ return _id; },
G :
function(){ return _g; }
}
this.State.Set = function(id,g){
_id
= id;
_g
= g;
}
var Checked = function()
{
// alert($P("passport_frm_1").readyStatus);
//
if($P("passport_frm_1").readyState == "complete")
//
{
//
alert("登陆成功!");
//
clearInterval(intervalId);
//
}
}
var ObServerLocation = function()
{
var hash = window.location.hash;
if ((hash.length >= 1) && (hash.charAt(0) == '#')){
hash
= hash.substring(1);
if(hash.indexOf("e_usr") > -1 || hash.indexOf("e_pss") > -1)
{
var err = "";
if(hash.indexOf("e_usr")> -1)
err
+= "用户名不能为空!";
if(hash.indexOf("e_pss")> -1)
err
+= "密码不能为空!";
clearInterval(intervalId);
alert(err);
}
else{
if(hash.length < 5 || hash == '0|'){
clearInterval(intervalId);
alert(
"用户名或者密码错误,登陆失败!");
}
else
{
var sp = hash.split('|');
if(sp.length == 3 && sp[0].length == 36 && !isNaN(sp[1]))
{
clearInterval(intervalId);
// if(encodeURI(me.UserName).toLowerCase() != sp[2].toLowerCase()){
//
me.UserName = "";
//
alert("登陆失败!");
//
}
//
else{
me.State.Set(sp[1],sp[0]);
//alert("登陆成功!")
var url = me.passport + "setlogin.aspx?g=" + me.State.G() + "&id=" + me.State.ID();
$P(
"passport_frm_1").src = url;
//intervalId = setInterval(Checked,5000);
//fn.call();
var s = new String(location.href);
location.href
= s.substring(0,s.indexOf("#")) + "#";
try{
if(OnSignEnd)OnSignEnd();
}
catch(e){}
// }
}
}
}
//clearInterval(intervalId);
}
}
this.OnSign = function(){
var username = $P("passport_username").value;
var password = $P("passport_password").value;
me.UserName
= username;
var url = me.passport + "jslogin.aspx?username=" + escape(username) + "&password=" + escape(password) + "&r=" + Math.random();
$P(
"passport_frm_1").src = url;

intervalId
= setInterval(ObServerLocation, 500);
}
var ObServerLogout = function()
{
var hash = window.location.hash;
if ((hash.length >= 1) && (hash.charAt(0) == '#')){
hash
= hash.substring(1);
if(hash == "0|")
{
clearInterval(intervalId);
var url = me.passport + "setlogout.aspx?r=" + Math.random();
$P(
"passport_frm_1").src = url;
try{
if(OnSignOutEnd)OnSignOutEnd();
}
catch(e){}
}
}
}
this.OnSignOut = function(){
var url = me.passport + "jslogout.aspx?r=" + Math.random();
$P(
"passport_frm_1").src = url;

intervalId
= setInterval(ObServerLogout,500);
};
}
//</script>
javascript使用很简单,点击登录也就是调用了OnSign方法,将会向passport.c.com/jslogin.aspx发出请求。
Response.AddHeader("P3P", @"CP=""CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR""");
Response.AddHeader(
"Content-Encoding:", "utf-8");

string url
= Request.ServerVariables["HTTP_REFERER"];
if (!string.IsNullOrEmpty(url) && url.IndexOf('#') > 0)
{
url
= url.Substring(0, url.IndexOf('#'));
}
string username
= Request["username"];
string password
= Request["password"];
Response.Write(
"<script>var f=parent;/*alert(f.document);*/f.location.href = '" + url + "' + '#' + ");
bool error
= false;
if (string.IsNullOrEmpty(username))
{
Response.Write(
"'e_usr");
error
= true;
}
if (string.IsNullOrEmpty(password))
{
if (error)
Response.Write(
"|");
else
Response.Write(
"'");
Response.Write(
"e_pss");
error
= true;
}
if (error)
{
Response.Write(
"';</script>");
Response.End();
}
password
= HttpUtility.UrlDecode(password, System.Text.Encoding.UTF8);
password
= EncryptHelper.MD5(password);
password
= password.Substring(8, 16);
BBSUser bu
= BBSUserHelper.Current(username, password);
if (bu.UserID != 0)
{
DateTime dt
= DateTime.Now;
string save
= RequestHelper.Get("save");
//dt = string.IsNullOrEmpty(save) ? dt.AddHours(2) : dt.AddMonths(1);
dt = dt.AddMonths(1);
Guid g
= Guid.NewGuid();
StatUserHelper.Delete(bu.UserID);
StatUser su
= new StatUser();
su.UserID
= bu.UserID;
su.UserName
= bu.UserName;
su.ExpireTime
= dt;
su.CreateTime
= DateTime.Now;
su.Guid
= g;
su.Password
= bu.Password;

su.ID
= StatUserHelper.Add(su);
string cachedate
= su.Guid.ToString() + "|" + su.ID;
string cacheuser
= bu.UserID + "|" + bu.UserName;
FormsAuthenticationTicket ticket
= new FormsAuthenticationTicket(1, cachedate, DateTime.Now, dt, true, cacheuser);
string authTicket
= FormsAuthentication.Encrypt(ticket);
HttpCookie UserCookie
= new HttpCookie(FormsAuthentication.FormsCookieName, authTicket);
UserCookie.Domain
= ".c.com";
UserCookie.Expires
= ticket.Expiration;
if (Response.Cookies[FormsAuthentication.FormsCookieName] == null)
Response.Cookies.Add(UserCookie);
else
Response.Cookies.Set(UserCookie);
Response.Write(
"'" + cachedate + "|" + HttpUtility.UrlEncode(bu.UserName, Encoding.UTF8) + "';</script>");
Response.End();
}
Response.Write(
"'0|';</script>");
Response.End();
jslogin的代码就是个验证的过程,加的P3P头,是可以跨域写入Cookie的保证。这里使用的是.Net 的Forms验证,要保持和其它域名加密方式以及名称的统一。假如有两个域同时指向一个站点的话。
这里返回javascript并且操作iframe的父窗口,改变地址,而引用的js会监视地址栏,发现数据,根据数据的格式,判断是否验证成功,如果成功了,那么会向各个站点下的一个SetLogin文件发出请求,当然被请求的页面需有P3P头。
这样在一个地方登录,实际上是同时向其它域名写入Cookie,退出的原理也是一样的。
但是,在Maxthon中Iframe操作父窗口地址这个操作是不允许的,它认为这个不安全,我倒是没觉得。这个问题还不太大,毕竟有Maxthon的用户不是太多,即使用了,告诉他不能用,他也会用IE。
但是IE8 beat 2这种操作方式将会弹出新窗口。在IE8  beat2中使用Iframe解决方案就会变得体验很不好。而且还给老衲带来了心灵上的伤害,以后不敢什么都写在客户端了,浏览器版本一变,对整体影响太大了。
下一个可替代方案就是使用反向代理,sohu的无刷新登录就是基于这个实现的据说,目前还在研究中。上一次因为时间急迫,没有时间仔细实验。这次是没办法躲过去了。