最近做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
{
///
/// 服务器端的登录页面的类
///
///
///
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();
}
///
/// 验证登录账号和密码是否正确
///
///
登录账号
///
登录密码
///
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", "");
return;
}
else if (ValidateUserInfo(Login1.UserName, Login1.Password) == false)
{
Page.RegisterClientScriptBlock("Add", "");
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);
}
}
}
}
&#160; &#160; &#160; &#160; &#160; 该类主要是将要发送的请求封装起来成为一个类进行发送!
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;
}
}
该类的主要作用是发送请求,或者将受到的请求进行处理,发后回送请求信息!
using System;
using System.Collections.Generic;
using System.Text;
using System.Web;
///
/// 该类的主要作用是发送请求,
/// 或者将受到的请求进行处理,发后回送请求信息!
///
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";
///
/// 添加需要提交的名和值
///
///
///
public void Add(string name, string value)
{
Inputs.Add(name, value);
}
///
/// 以输出Html方式POST
///
public void Post()
{
System.Web.HttpContext.Current.Response.Clear();
string html = string.Empty;
html += ("");
html += (string.Format("", FormName));
html += (string.Format("", FormName, Method, Url));
try
{
for (int i = 0; i
html += (string.Format("", Inputs.Keys[i], Inputs[Inputs.Keys[i]]));
}
html += ("");
html += ("");
System.Web.HttpContext.Current.Response.Write(html);
System.Web.HttpContext.Current.Response.End();
}
catch (Exception e)
{
}
}
}
安全验证类,主要对发送过来的票据进行验证!
using System; 输出用户登录账号
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Collections.Generic;
using System.Text;
///
/// 安全验证类
///
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";
}
///
/// 取得加密服务
///
///
static CryptoService GetCryptoService()
{
string key = GetAppKey();
string IV = GetAppIV();
CryptoService cs = new CryptoService(key, IV);
return cs;
}
///
/// 创建各分站发往认证中心的 Token
///
///
///
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;
}
}
///
/// 验证从各分站发送过来的 Token
///
///
///
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;
}
}
///
/// 创建认证中心发往各分站的 Token
///
///
///
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;
}
}
///
/// 验证从认证中心发送过来的 Token
///
///
///
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;
}
}
///
/// 创建 EAC 认证中心的 COOKIE
///
///
///
///
///
///
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;
}
}
///
/// 验证 EAC 认证中心的 COOKIE,验证通过时获取用户登录账号
///
///
///
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;
}
}
上边是主站中主要的类的代码,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。
在计算机发展的越来越快的时代,好多事情都要放到计算机中进行处理,那么系统也就会随之增多。所以单点登录也就会变得越来越重要!