最近做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 Sy
stem;
using Sy
stem.Data;
using Sy
stem.Con
figuration;
using Sy
stem.Web;
using Sy
stem.Web.S
ecurity;
using Sy
stem.Web.UI;
using Sy
stem.Web.UI.WebControls;
using Sy
stem.Web.UI.WebControls.WebParts;
using Sy
stem.Web.UI.HtmlControls;
using Sy
stem.Text;
public partial class _Default : Sy
stem.Web.UI.Page
{
/// <summary>
///
服务器真个
登录页面的类
/// </summary>
/// <p
aram name=sender></p
aram>
/// <p
aram name=e></p
aram>
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>
/// <p
aram name=userName>
登录账号</p
aram>
/// <p
aram name=userPwd>
登录密码</p
aram>
/// <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 Sy
stem;
using Sy
stem.Data;
using Sy
stem.Con
figuration;
using Sy
stem.Web;
using Sy
stem.Web.S
ecurity;
using Sy
stem.Web.UI;
using Sy
stem.Web.UI.WebControls;
using Sy
stem.Web.UI.WebControls.WebParts;
using Sy
stem.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>
/// <p
aram name=CurrentPage></p
aram>
/// <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 Sy
stem;
using Sy
stem.Collections.Generic;
using Sy
stem.Text;
using Sy
stem.Web;
/// <summary>
/// 该类的主要作用是发送要求,/// 或将遭到的要求进行处理,发后回送要求信息!
/// </summary>
public class PostService
{
private Sy
stem.Collections.Specialized.NameValueCollection Inputs = new Sy
stem.Collections.Specialized.NameValueCollection();
public string Url = ;
public string Method = post;
public string FormName = form1;
/// <summary>
///
添加需要提交的名和值
/// </summary>
/// <p
aram name=name></p
aram>
/// <p
aram name=value></p
aram>
public void Add(string name,string value)
{
Inputs.Add(name,value);
}
/// <summary>
/// 以
输出Html方式POST
/// </summary>
public void Post()
{
Sy
stem.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>);
Sy
stem.Web.HttpContext.Current.Response.Write(html);
Sy
stem.Web.HttpContext.Current.Response.End();
}
catch (Exception e)
{
}
}
}</span>
安全验证类,主要对发送过来的票据进行验证!
<span style=font-size:18px;>using Sy
stem;
using Sy
stem.Data;
using Sy
stem.Con
figuration;
using Sy
stem.Web;
using Sy
stem.Web.S
ecurity;
using Sy
stem.Collections.Generic;
using Sy
stem.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>
/// <p
aram name=ssoRequest></p
aram>
/// <returns></returns>
public static bool CreateAppToken(SSORequest ssoRequest)
{
string OriginalAuthenticator = ssoRequest.IASID + ssoRequest.TimeStamp + ssoRequest.AppUrl;
string AuthenticatorDigest = CryptoHelper.Co
mputeHashString(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>
/// <p
aram name=ssoRequest></p
aram>
/// <returns></returns>
public static bool ValidateAppToken(SSORequest ssoRequest)
{
string Authenticator = ssoRequest.Authenticator;
string OriginalAuthenticator = ssoRequest.IASID + ssoRequest.TimeStamp + ssoRequest.AppUrl;
string AuthenticatorDigest = CryptoHelper.Co
mputeHashString(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>
/// <p
aram name=ssoRequest></p
aram>
/// <returns></returns>
public static bool CreateEACToken(SSORequest ssoRequest)
{
string OriginalAuthenticator = ssoRequest.UserAccount + ssoRequest.IASID + ssoRequest.TimeStamp + ssoRequest.AppUrl;
string AuthenticatorDigest = CryptoHelper.Co
mputeHashString(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>
/// <p
aram name=ssoRequest></p
aram>
/// <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.Co
mputeHashString(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>
/// <p
aram name=userAccount></p
aram>
/// <p
aram name=timeStamp></p
aram>
/// <p
aram name=expireTime></p
aram>
/// <p
aram name=cookieValue></p
aram>
/// <returns></returns>
public static bool CreatEACCookie(string userAccount,string timeStamp,string expireTime)
{
string plainText = UserAccount= + userAccount + ;TimeStamp= + timeStamp + ;ExpireTime= + expireTime;
plainText += hashSplitter + CryptoHelper.Co
mputeHashString(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>
/// <p
aram name=userAccount>
输出用户登录账号</p
aram>
/// <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.Co
mputeHashString(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 Sy
stem;
using Sy
stem.Data;
using Sy
stem.Con
figuration;
using Sy
stem.Web;
using Sy
stem.Web.S
ecurity;
using Sy
stem.Web.UI;
using Sy
stem.Web.UI.WebControls;
using Sy
stem.Web.UI.WebControls.WebParts;
using Sy
stem.Web.UI.HtmlControls;
using Sy
stem.Text;
public partial class _Default : Sy
stem.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.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.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。
在计算机发展的愈来愈快的时期,好多事情都要放到计算机中进行处理,那末系统也就会随之增多。所以单点登录也就会变得愈来愈重要!