现在越来越多的系统迫于压力以及提高性能,很多站点都是采用多站点分布式运行,例如腾讯、新浪的站点就分成很多个频道,各个频道有独立的域名,独立的IP来支撑,这样一来各个站点之间就出现了统一认证的问题,也就是需要用户在一个站点登录,其他站点都能用的,且退出之后,各个站点都不能用,形成对用户的统一管理,避免了各个子系统之间的功能冗余。 

本文就作者自己的一些使用过的设计方法进行整理。使用过的方式主要两种,一是认证中心被动认证,二是认证中心主动认证,本文将介绍被动认证模式。

一、认证流程

 

 分布式系统用户统一认证浅析(一)–认证中心被动认证实现-编程知识网

主要的流程是,用户第一次访问系统A的页面a,当前系统(系统A)状态还没有登录,所以只能跳转到认证中心进行登录,登录之后,认证中心跳转回来站点A的页面a,该用户现在已经拥有了TokenToken由认证中心根据用户名以及登录时间生成,这个是唯一的字符串,每次生成都会不同),但是站点A不知道此Token是否合法,所以系统A自动跳转到认证中心,拿该Token进行验证,验证通过再跳转回来站点A,此时站点知道用户的Token合法,允许访问页面a。此种验证方法有个小问题就是,访问站点A的每个页面都需要到认证中心认证所拥有的Token是否合法(因为有可能用户会在其中一个子系统退出,退出后用户的旧Token为非法Token

 

 

二、认证实现(C#,VS2008)

首先,建立认证中心的Web站点,页面机构如下:

 分布式系统用户统一认证浅析(一)–认证中心被动认证实现-编程知识网

 

 

子站点SubWebSite工程,页面结构如下:

 分布式系统用户统一认证浅析(一)–认证中心被动认证实现-编程知识网

 

 

修改一下上边的验证示意图,得到页面调用顺序示意图:

 

 分布式系统用户统一认证浅析(一)–认证中心被动认证实现-编程知识网

来看实现代码:

SubWebSiteDefault.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();//需要重新登录
        //SessionUrl中都有Token
        if (Request.QueryString["Token"] != null && Session["Token"] != null)
        {
            //Url中的TokenSession中的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
欢迎转载,请注明出处。谢谢。