.Net单点登录详解 (SSO)

来源:互联网 发布:filco minila mac 编辑:程序博客网 时间:2024/05/23 22:40

最近做GXP(高校平台)的项目,因为里边有好多个子系统,例如有考试系统,评教系统,基础系统,新生入学系统,权限系统,如果每个系统都有自己的独立的登录的界面,那么就会有能访问这五个系统的人就要记住五套用户名,密码。哇,好累啊,五套!在这个背景下提出了单点登录(SSO)。

先来说说什么是单点登录(SSO),单点登录(Single Sign On),简称为 SSO,是目前比较流行的企业业务整合的解决方案之一。SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。单点登录简单来说,就一处登录,可以到处访问。

SSO的解决方案很多,比如收费的有UTrust、惠普灵动等,开源的有CAS、Smart SSO等,其中应用最为广泛的是CAS。但是因本人能力有限,没有做出cas在.net下的应用。所有这篇文章来介绍一下模拟cas形式做的一个登录,请大家多多批评指正。

让我们来看看cas的工作原理!

\

\

当用户在地址栏中输入我们想访问的地址后,例如输入基础系统的url地址,基础系统的服务器会检测有没有发过来票据(ST),如果没有,那么会跳转到认证中心服务器端,当发送到认证中心服务器端后,认证中心服务器端会检查有没有有没有发过来CooKie,如果没有,那么就会那么就会出现登录界面,让用户输入用户名,密码,然后回去数据库中进行验证,如果用户输入的正确,那么会生成一个票据(ST)和保存用户名的Cookie,票据(ST)中包括:要访问系统的URL,和进入该系统的唯一凭证。返回给浏览器,浏览器接受后,会保存Cookie,然后通过ST中的URL转到要访问系统的服务器,进入该服务器后,依然后检测有没有ST,现在又ST,那么他会拿着这个ST中的唯一凭证去认证中心服务器进行比对,看看该唯一凭证是不是认证中心服务器端生成的,如果是的话啊,那么就返回用户信息!并展示页面给用户!。<喎�"/kf/ware/vc/" target="_blank" class="keylink">vcD4KPHA+ICAgICAgICAgy7XBy9Xiw7S24KOsuPi087zSv7TSu7j2wP3X06Gjz8i/tNK7z8LV4rj2wP3X07XEveG5uaOhPC9wPgo8cD4gICAgICAgIDxpbWcgc3JjPQ=="/uploadfile/Collfiles/20141129/20141129083547208.jpg" alt="\">

这个例子中有三个项目,分别为:MasterSite,site1,site2.MasterSite 模拟认证中心服务器,site1模拟其中的一个应用,site2模拟另一个应用!

让我们看一下主要的代码!

验证服务器端的登录页的后台代码。首先检查发过来的请求中包含的信息都有什么,


using System;using System.Data;using System.Configuration;using System.Web;using System.Web.Security;using System.Web.UI;using System.Web.UI.WebControls;using System.Web.UI.WebControls.WebParts;using System.Web.UI.HtmlControls;using System.Text; public partial class _Default : System.Web.UI.Page {    /// <summary>    /// 服务器端的登录页面的类    /// </summary>    /// <param name="sender">    /// <param name="e">    protected void Page_Load(object sender, EventArgs e)    {        if (!IsPostBack)        {            SSORequest ssoRequest = new SSORequest();              #region 验证 Post 过来的参数            //--------------------------------            // 请求注销            if (!string.IsNullOrEmpty(Request["Logout"]))            {                Authentication.Logout();                return;            }            //--------------------------------            // 各独立站点标识            if (string.IsNullOrEmpty(Request["IASID"]))            {                return;            }            else            {                ssoRequest.IASID = Request["IASID"];            }             //--------------------------------            // 时间戳            if (string.IsNullOrEmpty(Request["TimeStamp"]))            {                return;            }            else            {                ssoRequest.TimeStamp = Request["TimeStamp"];            }             //--------------------------------            // 各独立站点的访问地址            if (string.IsNullOrEmpty(Request["AppUrl"]))            {                return;            }            else            {                ssoRequest.AppUrl = Request["AppUrl"];            }             //--------------------------------            // 各独立站点的 Token            if (string.IsNullOrEmpty(Request["Authenticator"]))            {                return;            }            else            {                ssoRequest.Authenticator = Request["Authenticator"];            }             ViewState["SSORequest"] = ssoRequest;             #endregion              //验证从分站发过来的Token            if (Authentication.ValidateAppToken(ssoRequest))            {                string userAccount = null;                 // 验证用户之前是否登录过                //验证 EAC 认证中心的 Cookie,验证通过时获取用户登录账号                if (Authentication.ValidateEACCookie(out userAccount))                {                    ssoRequest.UserAccount = userAccount;                     //创建认证中心发往各分站的 Token                    if (Authentication.CreateEACToken(ssoRequest))                    {                        Post(ssoRequest);                    }                }                else                {                    return;                }            }            else            {                return;            }        }    }      //post请求    void Post(SSORequest ssoRequest)    {        PostService ps = new PostService();         ps.Url = ssoRequest.AppUrl;         ps.Add("UserAccount", ssoRequest.UserAccount);        ps.Add("IASID", ssoRequest.IASID);        ps.Add("TimeStamp", ssoRequest.TimeStamp);        ps.Add("AppUrl", ssoRequest.AppUrl);        ps.Add("Authenticator", ssoRequest.Authenticator);         ps.Post();    }     /// <summary>    /// 验证登录账号和密码是否正确    /// </summary>    /// <param name="userName">登录账号    /// <param name="userPwd">登录密码    /// <returns></returns>    private bool ValidateUserInfo(string userName, string userPwd)    {        //从数据库中读取,验证登录账号和密码        //略...        return true;    }     protected void Login1_Authenticate(object sender, AuthenticateEventArgs e)    {        if (string.IsNullOrEmpty(Login1.UserName) || string.IsNullOrEmpty(Login1.Password))        {            Page.RegisterClientScriptBlock("Add", "<script lanuage=\"javascript\">alert('用户名密码不能为空!');</script>");            return;        }        else if (ValidateUserInfo(Login1.UserName, Login1.Password) == false)        {            Page.RegisterClientScriptBlock("Add", "<script lanuage=\"javascript\">alert('用户名密码错误!');</script>");            return;        }        else        {            //保存当前用户的用户名            Session["CurrUserName"] = Login1.UserName;            //设置过期时间  单位为分钟            Session.Timeout = 120;             SSORequest ssoRequest = ViewState["SSORequest"] as SSORequest;             // 如果不是从各分站 Post 过来的请求,则默认登录主站            if (ssoRequest == null)            {                FormsAuthentication.SetAuthCookie(Login1.UserName, false);                 ssoRequest = new SSORequest();                //主站标识ID                ssoRequest.IASID = "00";                ssoRequest.AppUrl = "SiteList.aspx";                ssoRequest.TimeStamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm");                ssoRequest.Authenticator = string.Empty;                 Response.Redirect("SiteList.aspx");            }            ssoRequest.UserAccount = Login1.UserName;             //创建Token            if (Authentication.CreateEACToken(ssoRequest))            {                string expireTime = DateTime.Now.AddHours(3).ToString("yyyy-MM-dd HH:mm");                 Authentication.CreatEACCookie(ssoRequest.UserAccount, ssoRequest.TimeStamp, expireTime);                 Post(ssoRequest);            }         }    }}
 该类主要是将要发送的请求封装起来成为一个类进行发送!using System;using System.Data;using System.Configuration;using System.Web;using System.Web.Security;using System.Web.UI;using System.Web.UI.WebControls;using System.Web.UI.WebControls.WebParts;using System.Web.UI.HtmlControls;/// /// SSORequest  该类主要是设置请求包,请求包中都包括哪些信息!/// 继承MarshalByRefObject ,主要解决跨域访问的问题!/// [Serializable]public class SSORequest : MarshalByRefObject{    public string IASID;         //各独立站点标识ID    public string TimeStamp;     //时间戳    public string AppUrl;        //各独立站点的访问地址    public string Authenticator; //各独立站点的 Token    public string UserAccount;   //账号    public string Password;      //密码    public string IPAddress;     //IP地址    //为ssresponse对象做准备    public string ErrorDescription = "认证失败";   //用户认证通过,认证失败,包数据格式不正确,数据校验不正确    public int Result = -1;    public SSORequest()    {    }    ///     /// 获取当前页面上的SSORequest对象    ///     ///     ///     public static SSORequest GetRequest(Page CurrentPage)    {        SSORequest request = new SSORequest();        request.IPAddress = CurrentPage.Request.UserHostAddress;        request.IASID = CurrentPage.Request["IASID"].ToString();// Request本身会Decode        request.UserAccount = CurrentPage.Request["UserAccount"].ToString();//this.Text        request.Password = CurrentPage.Request["Password"].ToString();        request.AppUrl = CurrentPage.Request["AppUrl"].ToString();        request.Authenticator = CurrentPage.Request["Authenticator"].ToString();        request.TimeStamp = CurrentPage.Request["TimeStamp"].ToString();        return request;    }}
该类的主要作用是发送请求,或者将受到的请求进行处理,发后回送请求信息!


<span style="font-size:18px;">using System;using System.Collections.Generic;using System.Text;using System.Web; /// <summary>/// 该类的主要作用是发送请求,/// 或者将受到的请求进行处理,发后回送请求信息!/// </summary>public class PostService{    private System.Collections.Specialized.NameValueCollection Inputs = new System.Collections.Specialized.NameValueCollection();    public string Url = "";    public string Method = "post";    public string FormName = "form1";     /// <summary>    /// 添加需要提交的名和值    /// </summary>    /// <param name="name">    /// <param name="value">    public void Add(string name, string value)    {        Inputs.Add(name, value);    }     /// <summary>    /// 以输出Html方式POST    /// </summary>    public void Post()    {        System.Web.HttpContext.Current.Response.Clear();         string html = string.Empty;         html += ("");        html += (string.Format("", FormName));        html += (string.Format("<form name="\"{0}\"" method="\"{1}\"" action="\"{2}\"">", FormName, Method, Url));        try        {            for (int i = 0; i < Inputs.Keys.Count; i++)            {                html += (string.Format("<input name="\"{0}\"" value="\"{1}\"" type="\"hidden\"">", Inputs.Keys[i], Inputs[Inputs.Keys[i]]));            }            html += ("</form>");            html += ("");             System.Web.HttpContext.Current.Response.Write(html);            System.Web.HttpContext.Current.Response.End();        }        catch (Exception e)        {                     }    }}</span>

安全验证类,主要对发送过来的票据进行验证!

<span style="font-size:18px;">using System;using System.Data;using System.Configuration;using System.Web;using System.Web.Security;using System.Collections.Generic;using System.Text; /// <summary>/// 安全验证类/// </summary>public class Authentication{    static readonly string cookieName = "EACToken";    static readonly string hashSplitter = "|";     public Authentication()    {    }     public static string GetAppKey(int appID)    {        //string cmdText = @"select * from ";        return string.Empty;    }     public static string GetAppKey()    {        return "22362E7A9285DD53A0BBC2932F9733C505DC04EDBFE00D70";    }     public static string GetAppIV()    {        return "1E7FA9231E7FA923";    }     /// <summary>    /// 取得加密服务    /// </summary>    /// <returns></returns>    static CryptoService GetCryptoService()    {        string key = GetAppKey();        string IV = GetAppIV();         CryptoService cs = new CryptoService(key, IV);        return cs;    }     /// <summary>    /// 创建各分站发往认证中心的 Token    /// </summary>    /// <param name="ssoRequest">    /// <returns></returns>    public static bool CreateAppToken(SSORequest ssoRequest)    {        string OriginalAuthenticator = ssoRequest.IASID + ssoRequest.TimeStamp + ssoRequest.AppUrl;        string AuthenticatorDigest = CryptoHelper.ComputeHashString(OriginalAuthenticator);        string sToEncrypt = OriginalAuthenticator + AuthenticatorDigest;        byte[] bToEncrypt = CryptoHelper.ConvertStringToByteArray(sToEncrypt);         CryptoService cs = GetCryptoService();         byte[] encrypted;         if (cs.Encrypt(bToEncrypt, out encrypted))        {            ssoRequest.Authenticator = CryptoHelper.ToBase64String(encrypted);             return true;        }        else        {            return false;        }    }      /// <summary>    /// 验证从各分站发送过来的 Token    /// </summary>    /// <param name="ssoRequest">    /// <returns></returns>    public static bool ValidateAppToken(SSORequest ssoRequest)    {        string Authenticator = ssoRequest.Authenticator;         string OriginalAuthenticator = ssoRequest.IASID + ssoRequest.TimeStamp + ssoRequest.AppUrl;        string AuthenticatorDigest = CryptoHelper.ComputeHashString(OriginalAuthenticator);        string sToEncrypt = OriginalAuthenticator + AuthenticatorDigest;        byte[] bToEncrypt = CryptoHelper.ConvertStringToByteArray(sToEncrypt);         CryptoService cs = GetCryptoService();        byte[] encrypted;         if (cs.Encrypt(bToEncrypt, out encrypted))        {            return Authenticator == CryptoHelper.ToBase64String(encrypted);        }        else        {            return false;        }    }      /// <summary>    /// 创建认证中心发往各分站的 Token    /// </summary>    /// <param name="ssoRequest">    /// <returns></returns>    public static bool CreateEACToken(SSORequest ssoRequest)    {        string OriginalAuthenticator = ssoRequest.UserAccount + ssoRequest.IASID + ssoRequest.TimeStamp + ssoRequest.AppUrl;        string AuthenticatorDigest = CryptoHelper.ComputeHashString(OriginalAuthenticator);        string sToEncrypt = OriginalAuthenticator + AuthenticatorDigest;        byte[] bToEncrypt = CryptoHelper.ConvertStringToByteArray(sToEncrypt);         CryptoService cs = GetCryptoService();        byte[] encrypted;         if (cs.Encrypt(bToEncrypt, out encrypted))        {            ssoRequest.Authenticator = CryptoHelper.ToBase64String(encrypted);             return true;        }        else        {            return false;        }    }      /// <summary>    /// 验证从认证中心发送过来的 Token    /// </summary>    /// <param name="ssoRequest">    /// <returns></returns>    public static bool ValidateEACToken(SSORequest ssoRequest)    {        string Authenticator = ssoRequest.Authenticator;         string OriginalAuthenticator = ssoRequest.UserAccount + ssoRequest.IASID + ssoRequest.TimeStamp + ssoRequest.AppUrl;        string AuthenticatorDigest = CryptoHelper.ComputeHashString(OriginalAuthenticator);        string sToEncrypt = OriginalAuthenticator + AuthenticatorDigest;        byte[] bToEncrypt = CryptoHelper.ConvertStringToByteArray(sToEncrypt);         string EncryCurrentAuthenticator = string.Empty;        CryptoService cs = GetCryptoService();        byte[] encrypted;         if (cs.Encrypt(bToEncrypt, out encrypted))        {            EncryCurrentAuthenticator = CryptoHelper.ToBase64String(encrypted);             return Authenticator == EncryCurrentAuthenticator;        }        else        {            return false;        }    }      /// <summary>    /// 创建 EAC 认证中心的 Cookie    /// </summary>    /// <param name="userAccount">    /// <param name="timeStamp">    /// <param name="expireTime">    /// <param name="cookieValue">    /// <returns></returns>    public static bool CreatEACCookie(string userAccount, string timeStamp, string expireTime)    {        string plainText = "UserAccount=" + userAccount + ";TimeStamp=" + timeStamp + ";ExpireTime=" + expireTime;        plainText += hashSplitter + CryptoHelper.ComputeHashString(plainText);         CryptoService cs = GetCryptoService();        byte[] encrypted;         if (cs.Encrypt(CryptoHelper.ConvertStringToByteArray(plainText), out encrypted))        {            string cookieValue = CryptoHelper.ToBase64String(encrypted);            SetCookie(cookieValue);             return true;        }        else        {            return false;        }    }     /// <summary>    /// 验证 EAC 认证中心的 Cookie,验证通过时获取用户登录账号    /// </summary>    /// <param name="userAccount">输出用户登录账号    /// <returns></returns>    public static bool ValidateEACCookie(out string userAccount)    {        userAccount = string.Empty;        try        {             string cookieValue = GetCookie().Value;            byte[] toDecrypt = CryptoHelper.FromBase64String(cookieValue);            CryptoService cs = GetCryptoService();             string decrypted = string.Empty;            if (cs.Decrypt(toDecrypt, out decrypted))            {                 string[] arrTemp = decrypted.Split(Convert.ToChar(hashSplitter));                string plainText = arrTemp[0];                string hashedText = arrTemp[1];                 userAccount = plainText.Split(Convert.ToChar(";"))[0].Split(Convert.ToChar("="))[1];                 return hashedText.Replace("\0", string.Empty) == CryptoHelper.ComputeHashString(plainText);             }            else            {                return false;            }        }        catch (Exception e)        {            return false;        }    }      public static void Logout()    {        HttpContext.Current.Response.Cookies[cookieName].Expires = DateTime.Parse("1900-1-1");        HttpContext.Current.Response.Cookies[cookieName].Path = "/";    }     private static void SetCookie(string cookieValue)    {        HttpContext.Current.Response.Cookies[cookieName].Value = cookieValue;        HttpContext.Current.Response.Cookies[cookieName].Expires = DateTime.Now.AddHours(24);        HttpContext.Current.Response.Cookies[cookieName].Path = "/";    }     private static HttpCookie GetCookie()    {        HttpCookie cookie = HttpContext.Current.Request.Cookies["EACToken"];        return cookie;    }}</span>

上边是主站中主要的类的代码,APP_CODE下边的其他两个类主要是进行数据加密用的,这里不再列出!下边看一下分站的代码,大部分的都和主站的代码一样,也包括一个SSORequest和一个PostServer还有另外三个类,下边看一下主页的后台代码

using System;using System.Data;using System.Configuration;using System.Web;using System.Web.Security;using System.Web.UI;using System.Web.UI.WebControls;using System.Web.UI.WebControls.WebParts;using System.Web.UI.HtmlControls;using System.Text; public partial class _Default : System.Web.UI.Page {    protected void Page_Load(object sender, EventArgs e)    {        if (!IsPostBack)        {            #region SSO 部分代码            SSORequest ssoRequest = new SSORequest();             if (string.IsNullOrEmpty(Request["IASID"]))            {                ssoRequest.IASID = "01";                ssoRequest.TimeStamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm");                ssoRequest.AppUrl = Request.Url.ToString();                Authentication.CreateAppToken(ssoRequest);                 Post(ssoRequest);            }            else if (!string.IsNullOrEmpty(Request["IASID"])                && !string.IsNullOrEmpty(Request["TimeStamp"])                && !string.IsNullOrEmpty(Request["AppUrl"])                && !string.IsNullOrEmpty(Request["UserAccount"])                && !string.IsNullOrEmpty(Request["Authenticator"]))            {                ssoRequest.IASID = Request["IASID"];                ssoRequest.TimeStamp = Request["TimeStamp"];                ssoRequest.AppUrl = Request["AppUrl"];                ssoRequest.UserAccount = Request["UserAccount"];                ssoRequest.Authenticator = Request["Authenticator"];                 if (Authentication.ValidateEACToken(ssoRequest))                {                    //从数据库中获取UserId                    Session["CurrUserName"] = Request["UserAccount"];                    Session.Timeout = 120;                    FormsAuthentication.SetAuthCookie(Request["UserAccount"], false);                    Response.Write(string.Format("{0},您好!欢迎来到site1,  >> 访问site2", ssoRequest.UserAccount));                }            }             ViewState["SSORequest"] = ssoRequest;             #endregion        }    }     void Post(SSORequest ssoRequest)    {        PostService ps = new PostService();        //认证中心(主站)地址        string EACUrl = "http://192.168.24.89:8085";        ps.Url = EACUrl;        //ps.Add("UserAccount", ssoRequest.UserAccount);        ps.Add("IASID", ssoRequest.IASID);        ps.Add("TimeStamp", ssoRequest.TimeStamp);        ps.Add("AppUrl", ssoRequest.AppUrl);        ps.Add("Authenticator", ssoRequest.Authenticator);         ps.Post();    }      //注销登录    protected void LinkButton2_Click(object sender, EventArgs e)    {        FormsAuthentication.SignOut();         SSORequest ssoRequest = new SSORequest();         ssoRequest.IASID = "01";        ssoRequest.TimeStamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm");        ssoRequest.AppUrl = Request.Url.ToString();         Authentication.CreateAppToken(ssoRequest);         PostService ps = new PostService();         //认证中心(主站)地址        string EACUrl = "http://192.168.24.89:8085";        ps.Url = EACUrl;         ps.Add("IASID", ssoRequest.IASID);        ps.Add("TimeStamp", ssoRequest.TimeStamp);        ps.Add("AppUrl", ssoRequest.AppUrl);        ps.Add("Authenticator", ssoRequest.Authenticator);         ps.Add("Logout", "true");         ps.Post();    }     //返回主站    protected void LinkButton1_Click(object sender, EventArgs e)    {        if (Session["CurrUserName"] != null)        {            Response.Redirect("http://192.168.24.89:8085/MasterSite/SiteList.aspx");        }    }}

可以看到有一个注销登录,他会清空主站的Cookie和分站的Cookie中的信息!

这里仅写了一些主要的类的代码,可以来这里下载DEMO。

在计算机发展的越来越快的时代,好多事情都要放到计算机中进行处理,那么系统也就会随之增多。所以单点登录也就会变得越来越重要!



原创粉丝点击