BS单点登陆(SSO)实现代码展示
SSO英文全称Single Sign On,单点登录。SSO是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。它包括可以将这次主要的登录映射到其他应用中用于同一个用户的登录的机制。它是目前比较流行的企业业务整合的解决方案之一
现在很多企业级应用都基本会去实现单点登陆功能,这样对于用户体验上会有不错的加强。不需要重复登陆多次。
如上图所示,整个SSO的实现最重要就是SSO服务器的实现形式。很多SSO都是自己编写服务来实现!在登陆的时候,一般都在电脑上取出一种唯一标识然后保存在SSO服务器,以这唯一标识去识别是否已经登陆!这是跨域的一种实现形式!
今天我所以说的简要示例,并不是跨域SSO实现,是在同一顶级域名下的SSO实现,因为在整个示例中都是站点为服务器!此示例的意义在于详述相关的基本实现原理,并不是要说一些多么深奥知识!下面大概陈述一下一些关键代码.
一:首先子站需要实现的是两部份,一个是获取到SSO服务器站点发送过来的相关信息,包括登陆后的TOKEN票据(加密)!一个是登出操作的页面,由SSO服务器调用后,负责清除本站点的登陆状态。
View Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using Fdays.SSO.Lib.Client.CryptoClass;
using Fdays.SSO.Lib.Client.Model;
using System.Configuration;
namespace Fdays.NOTMVCSite
{
public partial class GetServicePost : System.Web.UI.Page
{
protected void Page_Load( object sender, EventArgs e)
{
if (! IsPostBack)
{
if (Request[ " Plat " ] != null )
{
string plat = Request[ " Plat " ].ToString();
MO_Request request = CryptoServices.Decrypt(plat, Convert.ToInt32(ConfigurationManager.AppSettings[ " AppCode " ]));
if (Request[ " LogoutResult " ] == null )
{
Session[ " Token " ] = request.Token;
}
else
{
string logoutResult = Request[ " LogoutResult " ].ToString();
}
Response.Redirect(request.UserUrl);
}
}
}
}
}
View Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace Fdays.NOTMVCSite
{
public partial class SSOLogout : System.Web.UI.Page
{
protected void Page_Load( object sender, EventArgs e)
{
if (! IsPostBack)
{
// 必要操作
Session[ " Token " ] = null ;
// 站点登出时所需操作
// Session["Uid"] = null;
// Session["Cid"] = null;
// UMSvc.Account.Logout(MSContext);
}
}
}
}
二:访问子站点的时候需要做作状态判断,如果未登陆时,将跳转到SSO服务器登陆。例子中用的是方法三,将站点代号、用户访问页面、子站点与SSO服务器交互的地址发送到SSO服务器,方法中还有一个用于同步SSO服务器与子站COOKIE过期时间的方法(涉及到状态同步)!
View Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Fdays.SSO.Lib.Client;
using System.Configuration;
namespace Fdays.NOTMVCSite
{
public class SSOPage: System.Web.UI.Page
{
protected override void OnLoad(EventArgs e)
{
Filter();
base .OnLoad(e);
}
private static void Filter()
{
if (HttpContext.Current.Session[ " Token " ] == null )
{
// 方法1
// SiteOperate.Login(Convert.ToInt32(ConfigurationManager.AppSettings["AppCode"]));
// 方法2
// SiteOperate.Login(Convert.ToInt32(ConfigurationManager.AppSettings["AppCode"]), " http://localhost :5005/Test2.aspx");
// 方法3
SiteOperate.Login(Convert.ToInt32(ConfigurationManager.AppSettings[ " AppCode " ]), " http://localhost:5005/Test2.aspx " , " http://localhost:5005/Site/GetServicePost " );
}
else if (Authentication.isSetTokenTime( 1 )) // 更新Token过期时间
{
Authentication.SetTokenExpiredTime();
}
}
}
}
三:服务器需要做的是获取子站点的登陆信息(解密),进行登陆,登陆完毕后需要将生成的DUID形式唯一票据TOKEN,发送回子站点!!登陆完毕后,此时有两种形式保存用户信息,1.保存在COOKIE(涉及到安全问题)2.缓存服务器memcached。如果子站需要获取用户信息就需要再次与SSO服务器或缓存服务器做交互!以下为部份代码:
View Code
#region 【获取子站发送的Post数据】
/// <summary>
/// 【FUNC】: 获取子站发送的Post数据
/// 【NAME】: 李建标
/// 【TIME】: 2010-01-12 10:50:00
/// </summary>
// [HttpPost]
public void GetClientPost()
{
RSACryption rsaCryption = new RSACryption();
MO_Request request = new MO_Request();
try
{
#region 获取请求数据
if (! string .IsNullOrEmpty(Request.QueryString[ " Plat " ]) && ! string .IsNullOrEmpty(Request.QueryString[ " AppCode " ]))
{
string appCode = Request.QueryString[ " AppCode " ].ToString();
Authentication.AddSite(appCode);
request = CryptoServices.Decrypt(Request.QueryString[ " Plat " ].ToString(), Convert.ToInt32(appCode));
SSOCookie.SaveCookie( " MO_request " , DataConversion.getStrRequest(request), DateTime.Now.AddMinutes( 30 ), " / " );
SSOCookie.SaveCookie( " AppCode " , appCode.ToString(), DateTime.Now.AddMinutes( 30 ), " / " );
}
#endregion
#region 判断是否请求登出
if (! string .IsNullOrEmpty(Request.QueryString[ " Logout " ]))
{
TempData[ " Logout " ] = Request[ " Logout " ];
ClientLogoutProc();
return ;
}
#endregion
#region 注释
// Authentication.AddSite(Request["AppCode"]);
// if (!string.IsNullOrEmpty(Request["Plat"]) && !string.IsNullOrEmpty(Request["AppCode"]))
// {
// request = CryptoServices.Decrypt(Request["Plat"].ToString(), Convert.ToInt32(Request["AppCode"]));
// SSOCookie.SaveCookie("MO_request", DataConversion.getStrRequest(request), DateTime.Now.AddMinutes(30), "/");
// SSOCookie.SaveCookie("AppCode", Request["AppCode"], DateTime.Now.AddMinutes(30), "");
// }
// #endregion
// #region 判断是否请求登出
// if (!string.IsNullOrEmpty(Request["Logout"]))
// {
// TempData["Logout"] = Request["Logout"];
// ClientLogoutProc();
// return;
// }
#endregion
#region 判断是否已经登录
if (SSOCookie.IsExist( " Token " ))
{
if (String.IsNullOrEmpty(request.Token))
{
ClientLoginProc( new Guid(SSOCookie.GetCookie( " Token " )));
}
else
{
GetTandSetCookie(request);
}
return ;
}
else if (! string .IsNullOrEmpty(request.Token)) // 没有登录
{
SSOCookie.SaveCookie( " Token " , request.Token, DateTime.Now.AddMinutes( 30 ), " / " );
GetTandSetCookie(request);
return ;
}
// if(UMUser.Uid != null)
// {
// UMSvc.Logs.Debug(Fdays.SSO.Model.Aaron.Emun.LogType.LGUSEROPT, UMUser.Uid, "获取子站发送的Post数据成功!");
// }
#endregion
}
catch (InvalidCastException icx) // 数据转换出错
{
UMSvc.Account.Logout(MSContext);
UMSvc.Logs.Debug(Fdays.SSO.Model.Aaron.Emun.LogType.LGSSERROR, UMUser.Uid, " 获取子站发送的Post数据失败 --- 数据转换出错!: " + icx.Message);
Jscript.AlertAndRedirect( " 操作失败,请重新登录! " , " Login " );
}
catch (Exception ex)
{
UMSvc.Account.Logout(MSContext);
UMSvc.Logs.Debug(Fdays.SSO.Model.Aaron.Emun.LogType.LGSSERROR, UMUser.Uid, " 获取子站发送的Post数据失败: " + ex.Message);
Jscript.AlertAndRedirect( " 操作失败,请重新登录! " , " Login " );
}
finally
{
}
Response.Redirect( " ~/Authen/login " );
}
#endregion
View Code
#region 【从子站请求登录处理】
/// <summary>
/// 【FUNC】:处理由子站发录登录请求
/// 【NAME】:李建标
/// 【TIME】:2010-02-12 10:48:00
/// </summary>
/// <param name="token"> 票据Guid </param>
public void ClientLoginProc(Guid token)
{
bool isOk = false ;
try
{
PostProcess postProcess = new PostProcess();
MO_Request request = DataConversion.getMORequest(SSOCookie.GetCookie( " MO_request " ));
request.Token = token.ToString();
postProcess.Url = request.CallbackUrl;
postProcess.Add( " Plat " , CryptoServices.Encrypt(request, Convert.ToInt32(SSOCookie.GetCookie( " AppCode " ))));
postProcess.Add( " Request " , CryptoServices.EncryptRStr(SSOCookie.GetTCookie(token.ToString()), Convert.ToInt32(SSOCookie.GetCookie( " AppCode " ))));
SSOCookie.DelCookie( " MO_request " );
SSOCookie.DelCookie( " AppCode " );
postProcess.PostToClient();
isOk = true ;
}
catch (Exception ex)
{
UMSvc.Logs.Debug(Fdays.SSO.Model.Aaron.Emun.LogType.LGSSERROR, UMUser.Uid, " 处理从子站登录时的数据错误: " + ex.Message);
isOk = false ;
}
finally
{
if (isOk != true )
{
UMSvc.Account.Logout(MSContext);
Jscript.AlertAndRedirect( " 数据出错,请重新登录! " , " Login " );
}
}
}
#endregion
View Code
/// <summary>
/// 【FUNC】:将验证数据回传到子战点
/// 【作得】:李建标
/// 【TIME】:2010-02-12 14:11:00
/// </summary>
public void PostToClient()
{
System.Web.HttpContext.Current.Response.Clear();
StringBuilder sbPostHtml = new StringBuilder();
sbPostHtml.AppendFormat( " <html> " );
sbPostHtml.AppendFormat( " <head></head> " );
sbPostHtml.AppendFormat( " <body onload=\"document.{0}.submit()\"> " , FormName);
sbPostHtml.AppendFormat( " <form name=\"{0}\" method=\"post\" action=\"{2}\"> " , FormName, Method, Url);
try
{
foreach ( string keys in Inputs)
{
sbPostHtml.AppendFormat( " <input name=\"{0}\" type=\"hidden\" value=\"{1}\"> " , keys, Inputs[keys]);
}
sbPostHtml.AppendFormat( " </form> " );
sbPostHtml.AppendFormat( " </body> " );
sbPostHtml.AppendFormat( " </html> " );
System.Web.HttpContext.Current.Response.Write(sbPostHtml.ToString());
System.Web.HttpContext.Current.Response.End();
}
catch (Exception)
{
// 输入异常提示
}
finally
{
}
// StringBuilder sbCondition = new StringBuilder();
// sbCondition.Append("?Sign=100");
// foreach (string keys in Inputs)
// {
// sbCondition.AppendFormat("&{0}={1}", keys, Inputs[keys]);
// }
// System.Web.HttpContext.Current.Response.Redirect(Url + sbCondition.ToString());
// System.Web.HttpContext.Current.Response.End();
}
四:在子站点A跳转到子站点B,同样判断登陆状态!然后跳转到SSO服务器,因为登陆状态还是存在,所以服务器不需要再作登陆,只需要将保存的TOKEN直接发送到子站点B就可以了,而服务器需要做的工作就是,在已登陆站点中增加子站点B到站点队列中(做登出操作的时候用到)!相关代码与一、二一样,此处不做展示!
五:子站点拿出操作时,站点将带着TOKEN与站点代码跳转到服务器!服务器接受到子站点请求后,将清理本身保存的COOKIE与SESSION。然后遍历已登陆站点列表,发出拿出请求;
View Code
// 站点向服务器发出登出请求(子站点代码)
public void Logout()
{
// 方法1
SiteOperate.Logout(appCode, Session[ " Token " ].ToString());
// 方法2
// SiteOperate.Logout(appCode, Session["Token"].ToString(), " http://localhost :5002/Site/Test2");
// 方法3
// SiteOperate.Logout(appCode, Session["Token"].ToString(), " http://localhost :5002/Site/Test2", " http://localhost :5002/Site/GetServicePost");
}
View Code
#region 【从子站请求登出处理】
/// <summary>
/// 【FUNC】: 处理子站登出请求
/// 【NAME】: 李建标
/// 【TIME】: 2010-02-12 10:44:00
/// </summary>
public void ClientLogoutProc()
{
bool isOk = false ;
string Err = "" ;
MO_Request request = new MO_Request();
PostProcess postProcess = new PostProcess();
try
{
string strRequest = SSOCookie.GetCookie( " MO_request " );
request = DataConversion.getMORequest(strRequest);
postProcess.Url = request.CallbackUrl;
postProcess.Add( " Plat " , CryptoServices.Encrypt(request, Convert.ToInt32(SSOCookie.GetCookie( " AppCode " ))));
postProcess.Add( " LogoutResult " , " 登出成功! " );
isOk = true ;
}
catch (Exception ex)
{
Err = ex.StackTrace;
UMSvc.Logs.Debug(Fdays.SSO.Model.Aaron.Emun.LogType.LGSSERROR, UMUser.Uid, " 处理子站登出请求: " + Err);
isOk = false ;
}
finally
{
request = DataConversion.getMORequest(SSOCookie.GetCookie( " MO_request " ));
SSOCookie.DelCookie( " MO_request " );
SSOCookie.DelCookie( " AppCode " );
SSOCookie.DelCookie(SSOCookie.GetCookie( " Token " ).ToString());
SSOCookie.DelCookie( " Token " );
postProcess.LogoutPost(request);
if (SSOCookie.IsExist( " Token " ) && SSOCookie.GetCookie( " Token " ).ToString() == request.Token)
{
// SSOCookie.SetTokenExpiredTime(SSOCookie.GetCookie("Token").ToString(), DateTime.Now);
Authentication.DelUserSite();
// SSOCookie.DelCookie("Token");
}
if (isOk != true )
{
UMSvc.Logs.Debug(Fdays.SSO.Model.Aaron.Emun.LogType.LGSSERROR, UMUser.Uid, " 处理子站登出请求失败: " + Err);
Jscript.AlertAndGoBack( " 操作失败! " + Err);
}
}
}
#endregion
View Code
优点:实现比较简单,容易扩展。
缺点:安全性不高,不支持跨域访问;
上述代码只是部份代码,一些知识也并不是很全,如有错误也希望各位指出!大家共同学习学习。
如果有需要交流或者需要整份实现代码与数据库的请与我联系或留下邮箱地址!
分类: .NET
IHttpModule 与 IHttpHandler 浅释(一)
最近几天开始学习.NET运行的相关知识,其中涉及到 IHttpModule 与 IHttpHandler 两个部份,<br/>
也是我们每一个。NET程序中负责处理我们相应代码的两大部份!<br/>
在这里说说我对这两个部份的入门级认识,有什么错误之处希望大家可以指正指正!<br/>
首先是IHttpModule 接口,从MSDN中获得的代码如下:
代码
[AspNetHostingPermission(SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal), AspNetHostingPermission(SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
public interface IHttpModule{
// 释放资源
void Dispose();
void Init(HttpApplication context); // 初始化模块,并使其为处理请求做好准备
}
利用IHttpModule我们可以一些需要的功能:
例如:1:预处理 (验证、修改、过虑..)2:URL重写 3:访问日志 4:流量统计等等
第一个方法我相信大家也大概可以知道有什么作用了吧?
现在我们看下第二个方法:
在此方法中初始化了我们很多平时需要用到的数据:SERVER、REQUEST、REPONSE、Application、SESSION等等<br/>
此方法里也按照以下执行顺序由GLOBAL.ASPX文件中定义的模块与用户代码处理事件(.NET 2.0版本)
BeginRequest
AuthenticateRequest
PostAuthenticateRequest
AuthorizeRequest
PostAuthorizeRequest
ResolveRequestCache
PostResolveRequestCache
在 PostResolveRequestCache 事件之后、 PostMapRequestHandler 事件之前创建一个事件处理程序<br/>(对应于请求 URL 的页)。
PostMapRequestHandler
AcquireRequestState
PostAcquireRequestState
PreRequestHandlerExecute
执行事件处理程序。
PostRequestHandlerExecute
ReleaseRequestState
PostReleaseRequestState
在 PostReleaseRequestState 事件之后,响应筛选器(如果有)将对输出进行筛选。
UpdateRequestCache
PostUpdateRequestCache
EndRequest
以下是一个登录验证的例子,希望有助于大家去理解:
代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
namespace TestModule
{
public class LoginFilter : IHttpModule
{
s
public void Init(HttpApplication context)
{
// 以下四个事件中可以访问SESSION。
// context.PreRequestHandlerExecute += new EventHandler(context_PreRequestHandlerExecute);
// context.AcquireRequestState += new EventHandler(context_AcquireRequestState);
// context.PostAcquireRequestState += new EventHandler(context_PostAcquireRequestState);
context.PostRequestHandlerExecute += new EventHandler(context_PreRequestHandlerExecute);
}
public void context_PreRequestHandlerExecute( object sender, EventArgs e)
{
HttpApplication hApp = (HttpApplication)sender;
string URL = hApp.Context.Request.Url.ToString();
int la = URL.ToLower().ToString().IndexOf( " login.aspx " );
if (la == - 1 )
{
if (hApp.Context.Session[ " userId " ] == null )
{
hApp.Context.Response.Write( " <script language='javascript'>alert('您未登陆或登陆已超时,请重新登陆!');</script> " );
hApp.Context.Response.Redirect( " Login.aspx?source= " + path);
}
}
}
}
}
1:在需要应用的项目中增加TestModule.dll引用;
2:在项目中的Web.Config添加以下代码:
< httpModules >
< add name = " LoginFilter " type = "Test Module.LoginFilter,TestModule " />
</ httpModules >
补充:
1:在此例子中我们实现的是一个简单的登陆验证,在。NET2.0中的十六个事件中,其中只有PreRequestHandlerExecute、AcquireRequestState、PostAcquireRequestState、PostRequestHandlerExecute这4个可以访问SESSION.
2:在Web.config中, httpModules由名字空间、类名称和程序集名称组成
分类: 应用程序生命周期
作者: Leo_wl
出处: http://HdhCmsTestcnblogs测试数据/Leo_wl/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
版权信息查看更多关于BS单点登陆(SSO)实现代码展示的详细内容...