跨域登录(一)

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

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

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的无刷新登录就是基于这个实现的据说,目前还在研究中。上一次因为时间急迫,没有时间仔细实验。这次是没办法躲过去了。
posted @ 2008-11-04 22:26  Birdshover  阅读(13770)  评论(12编辑  收藏  举报