MVC - 单点登录中间件 (转)
http://www.cnblogs.com/wangrudong003/p/6435013.html
本章将要和大家分享的是一个单点登录中间件,中间件听起来高深其实这里只是吧单点登录要用到的逻辑和处理流程封装成了几个方法而已,默认支持采用redis服务保存session的方式,也可以使用参数Func<>方法来做自定义session存储操作的方式,就不用我默认提供的redis存储的方法了;要说本章内容的来源,其实是我在以前的ShenNiu.MVC管理系统中加入了最近做的调查问卷模块,这个问卷调查和ShenNiu.MVC不是一个站点,但是我的问卷调查系统可定在维护问卷或题目的时候需要登录人的信息,我又不想再单独弄一套账号方面的程序了,所以就采用这种单点登录模式,以此来提供调查问卷的所需要的用户信息,以及为了不久的将来自己写的某个模块也需要管理用户信息的话,就能省略掉用户模块了,不得不说单点登录在此刻发挥的作用之大;本章内容希望大家能够喜欢,也希望各位多多"扫码支持"和"推荐"谢谢!如果您想要和我们交流更多mvc相关信息可以来Ninesky框架作者:洞庭夕照 指定的官方群 428310563交流;
» 单点登录验证手画示例图
» ShenNiuApi.SDK封装中间件代码
» 调查问卷系统使用中间件示例
» 推广调查问卷系统
下面一步一个脚印的来分享:
» 单点登录验证手画示例图
首先,咋们要做一个简易的单点登录功能,需要明白其执行的流程和运作的原理,这里将图文并茂重点提出我认为关键的地方,先上一幅手工图:
看起来图画的不是很好看,不过我想表达的意思感觉还是表达清楚了;作为一个单点登录验证模块,最主要的流程有:
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