MVC - 单点登陆中间件 (转)

http://www.cnblogs.com/wangrudong003/p/6435013.htmlhtml

本章将要和你们分享的是一个单点登陆中间件,中间件听起来高深其实这里只是吧单点登陆要用到的逻辑和处理流程封装成了几个方法而已,默认支持采用redis服务保存session的方式,也可使用参数Func<>方法来作自定义session存储操做的方式,就不用我默认提供的redis存储的方法了;要说本章内容的来源,实际上是我在之前的ShenNiu.MVC管理系统中加入了最近作的调查问卷模块,这个问卷调查和ShenNiu.MVC不是一个站点,可是个人问卷调查系统可定在维护问卷或题目的时候须要登陆人的信息,我又不想再单独弄一套帐号方面的程序了,因此就采用这种单点登陆模式,以此来提供调查问卷的所须要的用户信息,以及为了避免久的未来本身写的某个模块也须要管理用户信息的话,就能省略掉用户模块了,不得不说单点登陆在此刻发挥的做用之大;本章内容但愿你们可以喜欢,也但愿各位多多"扫码支持"和"推荐"谢谢!若是您想要和咱们交流更多mvc相关信息能够来Ninesky框架做者:洞庭夕照 指定的官方群 428310563交流;git

 

» 单点登陆验证手画示例图web

» ShenNiuApi.SDK封装中间件代码redis

» 调查问卷系统使用中间件示例数据库

» 推广调查问卷系统api

 

下面一步一个脚印的来分享:浏览器

» 单点登陆验证手画示例图服务器

首先,咋们要作一个简易的单点登陆功能,须要明白其执行的流程和运做的原理,这里将图文并茂重点提出我认为关键的地方,先上一幅手工图:微信

看起来图画的不是很好看,不过我想表达的意思感受仍是表达清楚了;做为一个单点登陆验证模块,最主要的流程有:cookie

1. 未登陆时:提供统一登陆入口=》去数据库验证帐号正确性=》存储会话session(这里采用redis存储token和用户登录信息,利用其数据过时策略充当session会话机制)=》重定向到redirectUrl指定的地址

2. 已登陆时:获取站点的cookie存储的sessionId(token)=》调用验证token有效接口=》这里有两种状况(a,b)

    a) 有效token=》获取登陆用户的session存储的信息(redis存储的value信息)

    b) 无效token=》返回无效信息,构造登陆入口地址

经过上面分析,大体的流程应该很明确了下面咱们就来看封装的代码;

 

» ShenNiuApi.SDK封装中间件代码

这里要看的是中间件的3个方法:SsoMiddleWareServer(登陆入口操做),SsoMiddleWareClient(Token验证及获取登陆信息),SsoMiddleWareLoginOut(注销操做);这里我已经把方法打包放到了nuget上: Install-Package ShenNiuApi.SDK ,只须要下载最新的sdk,就能轻松帮您实现一个单点登陆架构,下面来看具体的代码;

SsoMiddleWareServer(登陆入口操做):

复制代码
 1         /// <summary>  2 /// 单点登陆操做 SSOMiddleWare服务端(方法功能:  3 /// 1.生成sessionId  4 /// 2.存储session到redis(60分钟失效)或者自定义sessionStoreFunc方法中  5 /// 3.构造带有token的重定向地址)  6 /// 注:默认采用redis保存session,所以须要在conf中配置ReadAndWritePorts和OnlyReadPorts两个appSettings节点:  7 /// ReadAndWritePorts在conf中配置格式如:pwd@ip:port,多个使用‘|’隔开 实例:shenniubuxing3@127.0.0.1:6377  8 /// OnlyReadPorts在conf中配置格式如:pwd@ip:port,多个使用‘|’隔开 实例:shenniubuxing3@127.0.0.1:6377  9 /// </summary> 10 /// <typeparam name="TUserBaseInfo">存储登陆信息的对象</typeparam> 11 /// <param name="userBaseInfo">登陆信息</param> 12 /// <param name="redirectUrl">重定向地址(注:格式应为http://或者https://;并通过UrlEncode转码后的地址;若是是同站点下面的话无需http://标记)</param> 13 /// <param name="token">执行方法无误后ref返回惟一的token(注:token生成规则是惟一的tokenKey+guid+时间戳)</param> 14 /// <param name="tokenKey">生成token的Key(默认:666666)</param> 15 /// <param name="sessionStoreFun">自定义session存储方法(提供自定义操做保存session的方法,覆盖默认的reids存储方式)</param> 16 /// <param name="timeOut">60(分钟)</param> 17 /// <returns>追加有token的重定向地址</returns> 18 public string SsoMiddleWareServer<TUserBaseInfo>(TUserBaseInfo userBaseInfo, string redirectUrl, ref string token, string tokenKey = "666666", Func<TUserBaseInfo, bool> sessionStoreFun = null, int timeOut = 60) 19 where TUserBaseInfo : class,new() 20  { 21 var returnUrl = string.Empty; 22 try 23  { 24 //非空验证 25 if (string.IsNullOrWhiteSpace(redirectUrl) || userBaseInfo == null) { return returnUrl; } 26 27 //生成Token 28 token = Md5Extend.GetSidMd5Hash(tokenKey); 29 30 // ShenNiuApi默认的Redis存储session 31 if (sessionStoreFun == null && userBaseInfo != null) 32  { 33 if (!CacheRepository.Current(CacheType.RedisCache).SetCache<TUserBaseInfo>(token, userBaseInfo, timeOut, true)) { return returnUrl; } 34  } 35 else { if (!sessionStoreFun(userBaseInfo)) { return returnUrl; } } 36 37 //通域名站内系统登陆 38 if (!Uri.IsWellFormedUriString(redirectUrl, UriKind.Absolute)) 39  { 40 returnUrl = redirectUrl; 41 return returnUrl; 42  } 43 44 #region 解析并构造跳转连接 45 redirectUrl = HttpUtility.UrlDecode(redirectUrl); 46 redirectUrl = redirectUrl.TrimEnd('&'); 47 redirectUrl = Regex.Replace(redirectUrl, "(&)?token=[^&]+(&)?", ""); 48 Uri uri = new Uri(redirectUrl); 49 var queryStr = uri.Query; 50 redirectUrl += queryStr.Contains('?') ? "" : "?"; 51 redirectUrl += string.IsNullOrWhiteSpace(queryStr.TrimStart('?')) ? "" : "&"; 52 returnUrl = string.Format("{0}token={1}", redirectUrl, token); 53 #endregion 54  } 55 catch (Exception ex) 56  { 57 throw new Exception(ex.Message); 58  } 59 finally 60  { 61 if (string.IsNullOrWhiteSpace(returnUrl)) { token = string.Empty; } 62  } 63 return returnUrl; 64 }
复制代码

SsoMiddleWareClient(Token验证及获取登陆信息):

复制代码
 1   /// <summary>  2 /// 单点登陆操做 SSOMiddleWare客户端(方法功能:  3 /// 1.验证客户端是否有sid或者url地址中带有最新的token  4 /// 2.获取服务端session的基本信息(注:默认直接读取服务端的redis库,同server方法同样须要配置对应的帐号节点ReadAndWritePorts和OnlyReadPorts)  5 /// 3.从新设置客户端cookie有效期和服务端存储session的有效期)  6 /// </summary>  7 /// <typeparam name="TUserBaseInfo">登录用户信息对象</typeparam>  8 /// <param name="httpContext">上下文HttpContext</param>  9 /// <param name="ssoLoginUrl">sso统一登录入口地址</param> 10 /// <param name="redirectUrl">待重定向的地址</param> 11 /// <param name="userBaseInfo">获取的登录用户信息</param> 12 /// <param name="token">惟一token(即:sid)</param> 13 /// <param name="getOrsetSessionFun">自定义获取服务端用户信息方法而且同时要知足从新设置新的session有效时间</param> 14 /// <param name="sidName">cookie保存的sid名称</param> 15 /// <param name="timeOut">过时时间</param> 16 /// <returns></returns> 17 public string SsoMiddleWareClient<TUserBaseInfo>(HttpContext httpContext, string ssoLoginUrl, string redirectUrl, ref TUserBaseInfo userBaseInfo, ref string token, Func<string, int, TUserBaseInfo> getAndsetSessionFun = null, string sidName = "sid", int timeOut = 60) 18 where TUserBaseInfo : class,new() 19  { 20 var returnUrl = string.Empty; 21 try 22  { 23 userBaseInfo = default(TUserBaseInfo); 24 token = string.Empty; 25 if (string.IsNullOrWhiteSpace(ssoLoginUrl) || string.IsNullOrWhiteSpace(redirectUrl) || string.IsNullOrWhiteSpace(sidName)) { return returnUrl; } 26 27 //设置过时后验证url串 28 returnUrl = string.Format("{0}?returnUrl={1}", ssoLoginUrl, HttpUtility.UrlEncode(redirectUrl)); 29 30 //获取token 31 var cookie = httpContext.Request.Cookies.Get(sidName); 32 token = httpContext.Request.Params["token"]; 33 token = string.IsNullOrWhiteSpace(token) ? (cookie == null ? "" : cookie.Value) : token; 34 if (string.IsNullOrWhiteSpace(token)) { return returnUrl; } 35 36 //获取用户基本信息 37 if (getAndsetSessionFun != null) 38  { 39 userBaseInfo = getAndsetSessionFun(token, timeOut); 40  } 41 else 42  { 43 userBaseInfo = CacheRepository.Current(CacheType.RedisCache).GetCache<TUserBaseInfo>(token, true); 44  } 45 if (userBaseInfo == null) 46  { 47 //过时cookie,清空 48 if (cookie != null) 49  { 50 cookie.Expires = DateTime.Now.AddDays(-1); 51  httpContext.Response.SetCookie(cookie); 52  } 53 return returnUrl; 54  } 55 56 //cookie被清除,须要从新设置 57 if (cookie == null) 58  { 59 cookie = new HttpCookie(sidName, token); 60 cookie.Expires = DateTime.Now.AddMinutes(timeOut); 61  httpContext.Response.AppendCookie(cookie); 62  } 63 else 64  { 65 //登录验证都成功后,须要从新设置cookie中的toke失效时间 66 cookie.Value = token; 67 cookie.Expires = DateTime.Now.AddMinutes(timeOut); 68  httpContext.Response.SetCookie(cookie); 69  } 70 71 //设置服务端session的失效时间 72 if (getAndsetSessionFun == null) 73  { 74  CacheRepository.Current(CacheType.RedisCache).AddExpire(token, timeOut); 75  } 76 returnUrl = string.Empty; 77  } 78 catch (Exception ex) 79  { 80 throw new Exception(ex.Message); 81  } 82 finally { if (!string.IsNullOrWhiteSpace(returnUrl)) { token = string.Empty; } } 83 return returnUrl; 84 }
复制代码

SsoMiddleWareLoginOut(注销操做):

复制代码
 1  /// <summary>  2 /// 单点登陆操做 SSOMiddleWare 退出登录  3 /// </summary>  4 /// <param name="httpContext">Http向下文</param>  5 /// <param name="removeSession">自定义移除方法</param>  6 /// <param name="sidName">cookie保存的sid名称</param>  7 /// <returns>true或false</returns>  8 public bool SsoMiddleWareLoginOut(HttpContext httpContext, Func<string, bool> removeSession = null, string sidName = "sid")  9  { 10 var isfalse = true; 11 try 12  { 13 if (string.IsNullOrWhiteSpace(sidName)) { sidName = "sid"; } 14 15 //获取cookie中的token 16 var cookie = httpContext.Request.Cookies.Get(sidName); 17 if (cookie == null) { return isfalse; } 18 19 //设置过时cookie(先过时cookie) 20 var key = cookie.Value; 21 cookie.Expires = DateTime.Now.AddDays(-1); 22  httpContext.Response.SetCookie(cookie); 23 24 //移除session 25 if (removeSession != null) 26  { 27 isfalse = removeSession(key); 28  } 29 else 30  { 31 isfalse = CacheRepository.Current(CacheType.RedisCache).Remove(key); 32  } 33  } 34 catch (Exception ex) 35  { 36 37 throw new Exception(ex.Message); 38  } 39 return isfalse; 40 }
复制代码

每一个方法的参数及做用,每行逻辑代码的都有注释,各位不妨研读下;这里要说的是每一个方法都默认有操做redis存储session的步骤,所以可以看出此中间件默认采用的是redis服务存储session;

有人会问为何会这样作,您单点登陆难道最底层用的不是接口来操做登陆或验证的吗?这里考虑有这样一个实用场景,做为一位中小型公司的员工来讲,接触到服务器一般部署了整个公司的站点好比:站点1,站点2...尽管域名不同可是都在同一台服务器上,再试想下若是用redis来存储session会话,此刻是否是就能认为我这台服务器就具备直接访问redis的读写权限(固然若是redis服务也在这台服务器上的话就更不用说了),那我直接在中间件中嵌入公共操做redis获取session,存储session等操做是否是都没问题,如此这般那咱们还须要单独弄一个session(token)验证的api么,不必的事情(对于单点登陆站点和重定向站点而言不必),所以我就这么干了,嵌入一个默认的redis操做哈哈(不服能够来辨);尽管如此不得不考虑更多的业务场景,万一登陆帐单和其余站点不在一个服务器(或者说没法直接访问redis呢),这里在3个中间件方法参数中提供了一个Func<>参数,每一个方法的Func<>表明额意思有点差异,各位能够看下注释;有了这个自定义Func,中间件就能识别若是客户端有传递此方法,那么以Func为主,没有就采用默认的方式操做redis,这样容许使用者自定义方法扩展了使用者本身认为调用token验证的api或者其余合理的方式,这也保证了方法的通用性。

 

» 调查问卷系统使用中间件示例

下面我将使用真实的实例来使用ShenNiuApi.SDK中的中间件方法,这里例子是在我调查问卷系统中如何使用;首先经过nuget下载 Install-Package ShenNiuApi.SDK 最新的sdk,而后须要在作登陆验证的Filter中或者继承Controller的父类中(我这里是后者)添加以下代码:

复制代码
 1 public class BaseController : Controller  2  {  3  4 protected StageModel.MoUserData _userData;  5  6 protected override void OnActionExecuting(ActionExecutingContext filterContext)  7  {  8  9 #region 采用ShenNiuApiClient的SsoClient中间件 10 11 ShenNiuApi.SDK.ShenNiuApiClient client = new ShenNiuApi.SDK.ShenNiuApiClient(); 12 13 var ssoLogin="http://www.lovexins.com:8081/User/Login"; 14 var redirectUrl = filterContext.HttpContext.Request.Url.AbsoluteUri; 15 var token = string.Empty; 16 var returnUrl = client.SsoMiddleWareClient<StageModel.MoUserData>(System.Web.HttpContext.Current, ssoLogin, redirectUrl, ref this._userData, ref token); 17 if (string.IsNullOrWhiteSpace(token) ) 18  { 19 filterContext.Result = new RedirectResult(returnUrl); 20 return; 21  } 22 #endregion 23  } 24 25 protected void ShowMsg(string msg) 26  { 27 28 ModelState.AddModelError(string.Empty, msg); 29  } 30 }
复制代码

只须要一句 client.SsoMiddleWareClient<StageModel.MoUserData>(System.Web.HttpContext.Current, ssoLogin, redirectUrl, ref this._userData, ref token); 便可完成问卷系统单点登陆的验证和获取登陆用户的信息,各类解析和设置sid的cookie信息都已经在中间件方法中完成了,是否是极大减小了您的编码量;为了对比下面我直接贴出没有使用SsoMiddleWareClient方法时候的代码量:

 1 protected override void OnActionExecuting(ActionExecutingContext filterContext)  2  {  3  4  5 var returnUrl = filterContext.HttpContext.Request.Url.AbsoluteUri;  6 returnUrl = HttpUtility.UrlEncode(returnUrl);  7 // var result = new RedirectResult(string.Format("http://www.lovexins.com:8081/User/Login?returnUrl={0}", returnUrl));  8 var result = new RedirectResult(string.Format("http://172.16.9.6:4040/User/Login?returnUrl={0}", returnUrl));  9 var key = "Sid"; 10 var timeOut = 30; 11 try 12  { 13 var cookie = filterContext.HttpContext.Request.Cookies.Get(key); 14 var token = filterContext.HttpContext.Request.Params["token"]; 15 token = string.IsNullOrWhiteSpace(token) ? (cookie == null ? "" : cookie.Value) : token; 16 if (string.IsNullOrWhiteSpace(token)) 17  { 18 filterContext.Result = result; 19 return; 20  } 21 22 this._userData = CacheRepository.Current(CacheType.RedisCache).GetCache<StageModel.MoUserData>(token, true); 23 if (this._userData == null && cookie != null) 24  { 25 //清空cookie 26 cookie.Expires = DateTime.Now.AddDays(-1); 27  filterContext.HttpContext.Response.SetCookie(cookie); 28 filterContext.Result = result; 29 return; 30  } 31 else if (this._userData == null) 32  { 33 filterContext.Result = result; 34 return; 35  } 36 37 if (cookie == null) 38  { 39 cookie = new HttpCookie(key, token); 40 cookie.Expires = DateTime.Now.AddMinutes(timeOut); 41  filterContext.HttpContext.Response.AppendCookie(cookie); 42  } 43 else 44  { 45 cookie.Value = token; 46 //登录验证都成功后,须要从新设置cookie中的toke失效时间 47 cookie.Expires = DateTime.Now.AddMinutes(timeOut); 48  filterContext.HttpContext.Response.SetCookie(cookie); 49  } 50 51 //设置session失效时间 52  CacheRepository.Current(CacheType.RedisCache).AddExpire(token, timeOut); 53  } 54 catch (Exception ex) 55  { 56 filterContext.Result = result; 57 return; 58  } 59 }
View Code

从代码量看前者简单多了,有人会说了您这不就是弄了一个方法而已嘛,说什么代码量少了哈哈;这不得不说一般咋们哎使用第三方的插件或者类库,这样极大减小了咋们工做量和提高了开发速度的好处,有了ShenNiuApi.SDK您还须要担忧什么呢;不过研究里面的具体步骤,逻辑代码我嘶吼很是同意的;

有了在调查问卷的自定义Controller父类后,咋们还须要有一个登陆的地方,这里我新建立的项目Stage.Web,在其登陆get请求的Action中增长了以下代码:

复制代码
 1    #region 采用ShenNiuApiClient的SsoClient中间件  2  3 ShenNiuApi.SDK.ShenNiuApiClient client = new ShenNiuApi.SDK.ShenNiuApiClient();  4 var ssoLogin = loginUrl;  5 var redirectUrl = context.Request.Path;  6  7 var token = string.Empty;  8 t = default(T);  9 var returnUrl = client.SsoMiddleWareClient<T>(System.Web.HttpContext.Current, ssoLogin, redirectUrl, ref t, ref token, sidName: UserLoginExtend.CookieName); 10 if (string.IsNullOrWhiteSpace(token)) 11  { 12 return new RedirectResult(returnUrl); 13  } 14 return null; 15 #endregion
复制代码

直接经过中间件提供的 SsoMiddleWareClient 方法获取登陆的token并验证是否已经登录过,若是登陆过了直接经过 return new RedirectResult(returnUrl); 重定向到returnUrl的地址中去;若是没有那么进入登陆界面,录入帐号信息后:

提交登陆,进入咋们post的Action中进过数据库对帐号匹配成功了,而后直接调用中间件方法来存储session并提供惟一的token值,再进行重定向跳转:

复制代码
 1  #region 采用ShenNiuApiClient的SsoServer中间件  2  3 ShenNiuApi.SDK.ShenNiuApiClient client = new ShenNiuApi.SDK.ShenNiuApiClient();  4  5 var timeOut = 60; //分钟  6 var token = string.Empty;  7 var redirectUrl = client.SsoMiddleWareServer<StageModel.MoUserData>(userData, returnUrl, ref token, timeOut: timeOut);  8 sbLog.AppendFormat("redirectUrl:{0},token:{1},", redirectUrl, token);  9 if (string.IsNullOrWhiteSpace(token) || string.IsNullOrWhiteSpace(redirectUrl)) 10  { 11 //登录失败 12 sbLog.Append("登录失败,"); 13  } 14 else 15  { 16 //写入Sso统一登录站点的sid到cookie 17 var cookie = new HttpCookie(UserLoginExtend.CookieName, token); 18 cookie.Expires = DateTime.Now.AddMinutes(timeOut); 19 cookie.Domain = Request.Url.Host; 20  HttpContext.Response.AppendCookie(cookie); 21  } 22 var isAddLog = await StageClass._WrigLogAsync(sbLog.ToString()); 23 return new RedirectResult(string.Format("{0}", redirectUrl)); 24 #endregion
复制代码

到此出sso的代码基本完成了就这么简单,不过这里默认采用的是我嵌入的redis服务来存储session信息的,因此还须要配置一个redis相关帐号密码等的节点,这里只须要您在 C:\Conf\ShenNiuApi.xml 磁盘下面增长以下名称的xml文件,文件内容也简单:

复制代码
 1 <ShenNiuApi>
 2 <RedisCache>  3 <!--读写权限服务地址,多个使用'|'隔开(格式如:pwd@ip:port)-->  4 <UserName>shenniubuxing3@111.111.111.152:1111</UserName>  5 <!--只读权限服务地址,多个使用'|'隔开-->  6 <UserPwd>shenniubuxing3@111.111.111.152:1111|shenniubuxing3@127.0.0.1:6377</UserPwd>  7 <ApiUrl></ApiUrl>  8 <ApiKey></ApiKey>  9 </RedisCache> 10 </ShenNiuApi>
复制代码

把内容里面的redis帐号,密码,端口,地址改为您本身的就好了;由于是在C盘中因此您服务器的其余站点也可以访问,假如您默认使用redis的方式存储session,那么只须要按照上面步骤就能快速的搭建一个单点登陆架构;这里我提供下调查问卷使用单点登陆测试的地址:www.lovexins.com:1001/Subject 测试帐号:shenniu003 密码:123123,注意登陆界面的域名和问卷调查的域名同样,只是端口不同而已,若是您要看效果能够在浏览器F12,而后如图操做:

可以看到这个sid就是地址栏中的token值,这就是咋们定义的sessionId拉,您不想试试吗。

 

» 推广调查问卷系统

调查问卷我想不少公司都会用到,你们通常都会本身作一套,我这里要为你们推荐的是神牛问卷,具体怎么试用呢,能够登陆地址http://www.lovexins.com:8081/User/Login 帐号:shenniiu003 密码:123123,进入系统后直接点击“问卷管理”=>"调查问卷",在这里您就能够添加您想调查的问卷信息和选项:

若是您添加完成问卷信息后,能够直接点击“阅览”查看您的问卷展现内容和方式(支持移动手机浏览访问),这也是填写调查问卷的人看到的界面,目前支持的题目类型有(单选,多选,文本输入),测试地址:http://www.lovexins.com:1001/shenniu003/wenjuan7,地址中的shenniu003是根据帐号来显示的,若是您是某个企业的hr或者老板这里地址栏能够直接注册成您公司的拼音名称或者汉字(是否是感受还能够呢):

关键点来了,有了填写的用户咋们须要分析并作统计,这个时候只须要您点击问卷列表中的"统计",就能看到以下名目的图表:

您能够点击某一个问题选项对应的“红色”条,直接进入用户选项的分析报表:

看起来效果仍是比较不错的吧,关键有数据统计给老板或者其余朋友看的时候,让人感受“高大上”,这是选项样式的统计图,那么若是是用户填写类的统计呢,是以下这样的列表:

特色:

1. 富含单选,多选,用户填写类的题目类型

2. 单点登陆架构,能快速嵌入到其余系统中

3. 手机web也能访问调查问问卷,问答问题

4. 详细的报表统计

5. 专业的维护人员哈哈

说明:最后要说的是此调查问卷系统是为了方便须要用到此功能的朋友和企业,若是您以为还能够想发一两个问卷调查内容,能够联系我并让我给您单独分配一个管理者帐号,固然若是您是某个企业带头人也想长久使用此调查系统能够联系邮箱:841202396@qq.com,随便您发多少问卷只要符合法定内容;

个人服装店,欢迎给位来捧场: 神牛衣柜3 若是您觉的此文还能够,或是新眼界,新知识,新眉脚等收获,请打赏下做者的分享,微信号:
 
分类: .net, webapi
 
好文要顶             关注我     收藏该文         
        
 
 
+加关注    
18    
0    
 
 
 
« 上一篇: MVC - 云服务器部署
相关文章
相关标签/搜索