现在越来越多的系统迫于压力以及提高性能,很多站点都是采用多站点分布式运行,例如腾讯、新浪的站点就分成很多个频道,各个频道有独立的域名,独立的IP来支撑,这样一来各个站点之间就出现了统一认证的问题,也就是需要用户在一个站点登录,其他站点都能用的,且退出之后,各个站点都不能用,形成对用户的统一管理,避免了各个子系统之间的功能冗余。
本文就作者自己的一些使用过的设计方法进行整理。使用过的方式主要两种,一是认证中心被动认证,二是认证中心主动认证,本文将介绍被动认证模式。
一、认证流程
主要的流程是,用户第一次访问系统A的页面a,当前系统(系统A)状态还没有登录,所以只能跳转到认证中心进行登录,登录之后,认证中心跳转回来站点A的页面a,该用户现在已经拥有了Token(Token由认证中心根据用户名以及登录时间生成,这个是唯一的字符串,每次生成都会不同),但是站点A不知道此Token是否合法,所以系统A自动跳转到认证中心,拿该Token进行验证,验证通过再跳转回来站点A,此时站点知道用户的Token合法,允许访问页面a。此种验证方法有个小问题就是,访问站点A的每个页面都需要到认证中心认证所拥有的Token是否合法(因为有可能用户会在其中一个子系统退出,退出后用户的旧Token为非法Token)
二、认证实现(C#,VS2008)
首先,建立认证中心的Web站点,页面机构如下:
子站点SubWebSite工程,页面结构如下:
修改一下上边的验证示意图,得到页面调用顺序示意图:
来看实现代码:
SubWebSite:Default.aspx
<form id="form1" runat="server">
<div>
通过了认证,登录的用户名为<%=Session["UserName"] %>
<asp:Button ID="btnSignOut" runat="server" Text="退出"
onclick="btnSignOut_Click" />
</div>
</form>
Default.aspx.cs:
protected void Page_Load(object sender, EventArgs e)
{
//当前子系统登录,且没有从认证中心取得Token
if (Session["Token"] == null && Request.QueryString["Token"] == null)
ReSignIn();//需要重新登录
//Session和Url中都有Token
if (Request.QueryString["Token"] != null && Session["Token"] != null)
{
//Url中的Token和Session中的Token不一致,Token已经失效
string urlToken = Request.QueryString["Token"];
string sessionToken = Request.QueryString["Token"];
if (!urlToken.Equals(sessionToken))
{
ReSignIn();//需要重新登录
}
else
{
string userName = Request["UserName"];
//在本地系统登录
if (SignInSubSys(userName))
{
Session["Token"] = string.Empty;
Response.End();//登录失败
}
}
}
//已经从认证中心取得Token,根据Token取认证中心取得用户信息
else if (Request.QueryString["Token"] != null && Session["Token"] == null)
{
Session["Token"] = Request.QueryString["Token"];
Response.Redirect("Http://mowen:8000/UserCenter/GetUserInfo.aspx?ReturnUrl=" +
HttpUtility.UrlEncode(Request.Url.AbsoluteUri) + "&Token=" + Request.QueryString["Token"]);
}
else
{
ReSignIn();
}
}
/// <summary>
/// 重新登录获取Token
/// </summary>
protected void ReSignIn()
{
Response.Redirect("http://mowen:8000/UserCenter/Default.aspx?ReturnUrl=" + HttpUtility.UrlEncode(Request.Url.AbsoluteUri));
}
/// <summary>
/// 在子站点进行登录
/// </summary>
/// <returns></returns>
protected bool SignInSubSys(string userName)
{
if (!string.IsNullOrEmpty(userName))
{
Session["UserName"] = userName;
}
return false;
}
/// <summary>
/// 退出登录
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected void btnSignOut_Click(object sender, EventArgs e)
{
//清空Session
Session.Clear();
//跳转到认证中心
Response.Redirect("Http://mowen:8000/UserCenter/SignOut.aspx?ReturnUrl=" +
HttpUtility.UrlEncode(Request.Url.AbsoluteUri));
}
认证中心获取Token页面(Default.aspx.cs):
protected void Page_Load(object sender, EventArgs e)
{
//来源的Url(此Http请求上一个请求,由上一个请求使用参数传递)
var returnUrl = string.Empty;
if (Request["ReturnUrl"] != null)
returnUrl = HttpUtility.HtmlDecode(Request["ReturnUrl"].Trim());
//根据Session来判断用户是否已经登录
if(Session["UserName"] != null && Session["Token"] != null)
{
//附加上Token参数并告诉子系统当前登录的用户名
if (returnUrl.IndexOf("?") > 0)
returnUrl = returnUrl + "&Token=" + Session["Token"] + "&UserName=" + Session["UserName"];
else
returnUrl = returnUrl + "?Token=" + Session["Token"] + "&UserName=" + Session["UserName"];
//已经登录
//跳转到原地址并加上Token
Response.Redirect(returnUrl);
}
else
{
//没有登录,跳转到登录页面
Response.Redirect("SignIn.aspx?ReturnUrl=" + HttpUtility.UrlEncode(returnUrl));
}
}
认证中心登录页面:
SignIn.aspx:
<form id="form1" runat="server">
<div>
用户名:<asp:TextBox ID="txtUserName" runat="server"></asp:TextBox><br />
密码:<asp:TextBox ID="txtPwd" runat="server"></asp:TextBox><br />
<asp:Button ID="btnSignIn" runat="server" Text="登录" onclick="btnSignIn_Click" />
</div>
</form>
SignIn.aspx.cs:
protected void btnSignIn_Click(object sender, EventArgs e)
{
Session["UserName"] = txtUserName.Text.Trim();
Session["Token"] =
System.Web.Security.FormsAuthentication.HashPasswordForStoringInConfigFile(
txtUserName.Text.Trim() + DateTime.Now, "md5");
//*************************************************************************
//将Token和用户信息关联起来
Hashtable htTokenInfo = null;
if (Application["UserTokenInfo"] != null)
htTokenInfo = (Hashtable)Application["UserTokenInfo"];
if (htTokenInfo == null)
htTokenInfo = new Hashtable();
if (!htTokenInfo.ContainsKey(Session["Token"]))
htTokenInfo.Add(Session["Token"], txtUserName.Text.Trim());
else
htTokenInfo[Session["Token"]] = txtUserName.Text.Trim();
Application["UserTokenInfo"] = htTokenInfo;
//*************************************************************************
//来源的Url(此Http请求上一个请求,由上一个请求使用参数传递)
var returnUrl = string.Empty;
if (Request["ReturnUrl"] != null)
returnUrl = HttpUtility.HtmlDecode(Request["ReturnUrl"].Trim());
returnUrl = BuildUrl(returnUrl, "Token", Session["Token"].ToString());
//跳转回去源地址
Response.Redirect(returnUrl);
}
/// <summary>
/// 功能:url里有key的值,就替换为value,没有的话就追加.
/// 执行过程:(1)、使用正则表达式匹配key
/// (2)、将匹配的Key的值替换为空的值
/// (3)、使用新值进行替换
/// 作者:莫文
/// 时间:2010-5-30 13:48
/// Email:mowen@founder.com
///
/// </summary>
/// <param name="url">要替换Url参数值的Url</param>
/// <param name="paramText">要替换值的参数名</param>
/// <param name="paramValue">要被替换的参数的值</param>
/// <returns>替换参数值后的Url</returns>
public static string BuildUrl(string url, string paramText, string paramValue)
{
//使用正则匹配所要查找的Key
Regex reg = new Regex(string.Format("{0}=[^&]*", paramText), RegexOptions.IgnoreCase);
Regex reg1 = new Regex("[&]{2,}", RegexOptions.IgnoreCase);
//将旧值替换为空
string _url = reg.Replace(url, "");
//新值
if (_url.IndexOf("?") == -1)
_url += string.Format("?{0}={1}", paramText, paramValue);//?
else
_url += string.Format("&{0}={1}", paramText, paramValue);//&
//将新值替换到Url中
_url = reg1.Replace(_url, "&");
//将多余的&字符清除
_url = _url.Replace("?&", "?");
return _url;
}
认证中心验证Token验证页面(GetUserInfo.aspx.cs):
protected void Page_Load(object sender, EventArgs e)
{
Hashtable htTokenInfo = null;
if (Application["UserTokenInfo"] != null)
htTokenInfo = (Hashtable)Application["UserTokenInfo"];
//来源的Url(此Http请求上一个请求,由上一个请求使用参数传递)
var returnUrl = string.Empty;
if (Request["ReturnUrl"] != null)
returnUrl = HttpUtility.HtmlDecode(Request["ReturnUrl"].Trim());
string token = Request["Token"];
if (htTokenInfo == null || string.IsNullOrEmpty(token))
{
//没有登录,跳转到登录页面
Response.Redirect("SignIn.aspx?ReturnUrl=" + HttpUtility.UrlEncode(returnUrl));
}
else
{
//用户信息是否和Session关联
if (htTokenInfo[token] == null)
//没有登录,跳转到登录页面
Response.Redirect("SignIn.aspx?ReturnUrl=" + HttpUtility.UrlEncode(returnUrl));
//附加上Token参数
if (returnUrl.IndexOf("?") > 0)
returnUrl = returnUrl + "&UserName=" + htTokenInfo[token];
else
returnUrl = returnUrl + "?UserName=" + htTokenInfo[token];
//跳回原页面
Response.Redirect(returnUrl);
}
}
认证中心验证退出页面(SignOut.aspx.cs):
protected void Page_Load(object sender, EventArgs e)
{
//清空Session
Session.Clear();
var returnUrl = string.Empty;
if (Request["ReturnUrl"] != null)
returnUrl = HttpUtility.HtmlDecode(Request["ReturnUrl"].Trim());
//跳转到登录页面
Response.Redirect("SignIn.aspx?ReturnUrl=" + HttpUtility.UrlEncode(returnUrl));
}
代码下载:http://www.cnfounder.com/mw/UserCenterDemo.rar
欢迎转载,请注明出处。谢谢。