程序员人生 网站导航

.Net单点登录详解 (SSO)

栏目:php教程时间:2014-12-15 08:48:36

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

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

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

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

                       

            

        当用户在地址栏中输入我们想访问的地址后,例如输入基础系统的url地址,基础系统的服务器会检测有无发过来票据(ST),如果没有,那末会跳转到认证中心服务器端,当发送到认证中心服务器端后,认证中心服务器端会检查有无有无发过来CooKie,如果没有,那末就会那末就会出现登录界面,让用户输入用户名,密码,然后回去数据库中进行验证,如果用户输入的正确,那末会生成1个票据(ST)和保存用户名的Cookie,票据(ST)中包括:要访问系统的URL,和进入该系统的唯1凭证。返回给阅读器,阅读器接受后,会保存Cookie,然后通过ST中的URL转到要访问系统的服务器,进入该服务器后,仍然后检测有无ST,现在又ST,那末他会拿着这个ST中的唯1凭证去认证中心服务器进行比对,看看该唯1凭证是否是认证中心服务器端生成的,如果是的话啊,那末就返回用户信息!并展现页面给用户!。

         说了这么多,给大家看1个例子。先看1下这个例子的结构!

        

         这个例子中有3个项目,分别为:MasterSite,site1,site2.MasterSite 摹拟认证中心服务器,site1摹拟其中的1个利用,site2摹拟另外一个利用!

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

验证服务器真个登录页的后台代码。首先检查发过来的要求中包括的信息都有甚么,

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> /// <param name="e"></param> 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> /// <param name="userPwd">登录密码</param> /// <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); } } } }
</pre><p></p><p>        <span style="font-size:18px">  该类主要是将要发送的要求封装起来成为1个类进行发送!</span></p><p><span style="font-size:18px"></span></p><pre name="code" class="html">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; /// <summary> /// SSORequest 该类主要是设置要求包,要求包中都包括哪些信息! /// 继承MarshalByRefObject ,主要解决跨域访问的问题! /// </summary> [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 = ⑴; public SSORequest() { } /// <summary> /// 获得当前页面上的SSORequest对象 /// </summary> /// <param name="CurrentPage"></param> /// <returns></returns> 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> /// <param name="value"></param> 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><head>"); html += (string.Format("</head><body onload="document.{0}.submit()">", 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}" type="hidden" value="{1}">", Inputs.Keys[i], Inputs[Inputs.Keys[i]])); } html += ("</form>"); html += ("</body></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"></param> /// <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"></param> /// <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"></param> /// <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"></param> /// <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> /// <param name="timeStamp"></param> /// <param name="expireTime"></param> /// <param name="cookieValue"></param> /// <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">输出用户登录账号</param> /// <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("", 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⑴⑴"); 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下边的其他两个类主要是进行数据加密用的,这里不再列出!下边看1下分站的代码,大部份的都和主站的代码1样,也包括1个SSORequest和1个PostServer还有另外3个类,下边看1下主页的后台代码

       

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, >> 访问<a href="http://localhost:6332/Site2/Default.aspx">site2</a>", 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"); } } }

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

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

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

------分隔线----------------------------
------分隔线----------------------------

最新技术推荐