今天遇到了一个问题. 公司有两个产品需要整合,
需要实现一个SSO... 后台做完了, 到前台这里发现了个巨大的问题:
因为这两套系统是在两个域名, 系统A在完成验证后,
用户在A系统页面关闭前, 超时范围内打开B系统都不需要再次登录.
但是, B系统凭什么认为一个新发起的请求(HttpRequest)是已经验证过身份的呢?
众所周知, Cookie不能跨域. 两系统共用Cookie的方式难度较大.安全性也有问题.
头疼, 开论坛. 手贱一般的点击了 "查看网页源代码"...
这个网页的源码中有一句话让我眼前一亮:
<script language="javacript"
src="http://ad.baidu.com/......."></script>...
对阿, 对script脚本的引用是可以跨域的啊!!
恩, 好办法. 写个测试试一下. 下图为设计思路:

大体思路为: 首先在B系统的那个需要A系统数据的那个页面,
添加一个脚本引用. 注意, 这里的脚本引用不指向一个真实的JS文件, 而是指向A系统的一个WebHandler或者ASHX.
这样在打开B系统的页面时, 就会调用A系统的那个WebHandler(或ASHX),
而这个WebHandler(或ASHX)因为属于A系统, 所以可以调用A系统中当前请求的Session或者Cookie.
然后以脚本输出的方式输出成几个带有来自A系统数据的Javascript字段.
然后B系统的页面就可以通过脚本访问到字段的值. 也就是说这个WebHandler(ASHX)就充当了桥梁.
至于之后是直接显示在页面上啊还是提交到服务器就是后话了..
不明白啊? 举个例子: B公司需要A公司提供一些文本资料,
可是B公司的员工无权直接去A公司拿取资料, 因为A公司的人不认识他. 那么有一个办法, 就是B公司请求A公司派一个
"大使" 将资料送来.
那么这个 "大使" 既可以从A公司拿取资料,
因为他本身就是A公司的人, 也可以将资料交付给B公司. 因为身在B公司的办公室.
好, 首先建立两个Web工程, 分别部署在两个域名下(不过我这儿只有localhost,
就用两个端口分开). 一个是信息提供方, 一个是数据接受方. 这个是信息提供方的主页面.
因为是测试, 所以页面什么都没有. 我们在默认页中在Cookie中添加一些东西...
(Session也行. 如果想要做A系统成功登陆关闭页面后超时前B系统自动登陆的话, 就用cookie)
public partial class _Default
: System.Web.UI.Page
{
protected
void Page_Load(object sender, EventArgs e)
{
Random
r=new Random(10000);
Int32 i = r.Next();
i
+= (((DateTime.Now.Year + DateTime.Now.Month + DateTime.Now.Day
+ DateTime.Now.Hour
+ DateTime.Now.Minute + DateTime.Now.Second +
DateTime.Now.Millisecond) / 2) * i);
//这里只是为了提高随机性..
HttpCookie c= new HttpCookie("TEA", i.ToString());
c.Expires
= DateTime.Now + TimeSpan.FromMinutes(50);
//必须设置超时时间,
否则还没等你开第二个浏览器窗口的时候Cookie就已经过期了!
Response.SetCookie(c);
Response.Write(i);
//测试结果应该是B系统的Default.aspx也显示这个.
}
}
下面这个就是用于处理从B系统页面发出的“伪JS”请求的HttpHandler了.
public class JSRequetHandler:IHttpHandler /*,IRequiresSessionState (HttpHandler如果要用Session的话必须继承这个)*/
{
#region IHttpHandler 成员
public bool IsReusable
{
get { return true; }
}
public void ProcessRequest(HttpContext context)
{
if (context.Request.Cookies["TEA"] == null)
{
context.Response.Write("var login_status = 0"); //可能是Session丢失或者非法请求.
}
else
{
context.Response.Write("var login_status = '" + context.Request.Cookies["TEA"].Value + "';");
//将上面存入Cookie中的 "TEA" 的值输出为 JS字段 "login_status" 的值. 这样B系统的页面脚本就能拿到值了.
}
}
#endregion
}
因为我这里用的是HttpHandler, 所以配置文件中还得写点东西.
如果是ASHX的话就不用写了.
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<!--.....-->
<system.web>
<!--.....-->
<httpHandlers>
<add verb="*" path="A.cj" validate="false" type="Handler.JSRequetHandler,
Handler, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
<!--所有访问A.cj(忽略路径)的请求都会由 "JSRequetHandler" 处理.-->
<!--.....-->
</httpHandlers>
</system.webServer>
</configuration>
调用页
<%@ Page Language="C#" AutoEventWireup="true" %>
<!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>B</title>
<script language="javascript" type="text/javascript" src="http://201.86.189.40:5678/TestWebSite01/A.cj"></script>
<script runat="server">
public void OnButtonServerClick(Object sender, EventArgs arg)
{
String fromA = mk.Value;
//A系统的数经过千辛万苦终于抵达B系统的后台.
}
</script>
</head>
<body>
<br />
<br />
<br />
<br />
<center style="font-size:60pt; font-family:黑体;" id="t"></center>
<br />
<br />
<form id="f" runat="server" style="text-align:center;">
<input type="hidden" id="mk" runat="server" />
<button id="btn" runat="server" onserverclick="OnButtonServerClick">提交到服务器</button>
</form>
<script language="javascript" type="text/javascript">
var _t = document.getElementById("t");
if(typeof(login_status) != 'undefined')
{
//A系统由JSRequestHandler输出的脚本已经完成输出, 数据已经保存在字段里了.
_t.innerHTML = login_status;
document.getElementById("mk").value = login_status;
}
else
{
_t.innerHTML="-";
//如果未定义, 那么表明A系统并未提供数据.
}
</script>
</body>
</html>
测试结果: 先打开A系统(就是从上数第一个Default页所在的那个Web工程)
启动后页面会输出一串数字, 然后启动B系统(调用页所在的Web工程), 所看到的数字是一致的. 测试成功.
Ps:据说W3C已经出了CrossDomain的规范了?
不过无论怎样, IE6还是不支持这个规范的... 毕竟中国还有好多人还在用IE6... |