2_MVC+EF+Autofac(dbfirst)轻型项目框架_用户权限验证
前言
接上面兩篇?0_MVC+EF+Autofac(dbfirst)輕型項目框架_基本框架?與?1_MVC+EF+Autofac(dbfirst)輕型項目框架_core層(以登陸為例)?。在第一篇中介紹了此架構的基本分層,在第二篇中,以登陸功能為例,介紹了項目的代碼結構。在本篇中將通過過濾器實現用戶權限驗證功能。
同樣,文中有問題的地方歡迎批評指正!謝謝!
開發背景?
在一個常規系統中權限驗證是不可缺的,在較簡單的系統中,用戶只會被簡單歸為登陸用戶和游客,而在較為復雜的系統中,除了判斷用戶是否登陸外,還需提供一套可靠的機制來驗證用戶是否擁有執行其請求的操作的權限。在ASP.Net MVC框架中的AuthorizeAttribute過濾器很好的滿足了這個要求。在我項目中,也是通過AuthorizeAttribute來實現對用戶權限的判斷。其中為了便捷,引入了Helper類來統一管理所有的Session和Cookie。
創建過程
?1.權限驗證模式與表結構
MVC框架中存在的控制器提供了很好的權限范圍的單位,所以在我的框架中,權限是驗證是以過濾器為基礎的,一個過濾器代表一個權限,不同的身份擁有不同的權限集合,而每一個用戶歸屬于一個身份。例如用戶甲的身份為超級管理員身份,他可能同時具有A,B,C,D,E,F這六個權限;而權限相對較低的用戶乙為普通管理員身份,他可能僅僅具備A,B,C,D這四個權限。
數據庫的表結構如下圖:
可以看到,在教師表中存在一個字段用來標志對應的Power的ID(PID),power表記錄所有身份(power這個詞用的有點怪 :-)),Authority表中記錄了所有權限,解釋下Authority中每一個字段的具體含義:
AUID:該權限的對應ID。
AUPID:該權限隸屬的父權限,例如:學生管理,教師管理的父級權限可能為管理中心。
Name:權限的名字。
AOrder:為了方便將權限轉化為用戶可見的控件操作樹,安排權限的排名先后順序。
URL:這個權限所對應打開的URL。
Controller:這個權限對應的控制器。
Power與Authority之間的AuthorityToPower映射了每一個身份所擁有的權限。
?2.AuthorizeAttribute過濾器
在介紹AuthorizeAttribute之前有必要先介紹下我web的結構,通過下圖可以看到在根路由兩個控制器的基礎上,我還擁有兩個區域,分別為學生和教師。所以在AuthorizeAttribute過濾器中,通過判斷區域名來實施具體的權限驗證操作。結合業務邏輯,在學生區域下,過濾器的功能僅僅是判斷學生的登陸狀態,如未登陸跳轉到登陸界面;而在教師區域下,除了要判斷是否登陸外還要判斷其所請求的控制器是否在他的權限集合中。
AuthorizeAttribute中代碼如下
1 using EDUA_ICore; 2 using System; 3 using System.Collections.Generic; 4 using System.Linq; 5 using System.Web; 6 using System.Web.Mvc; 7 8 namespace EDUA_WEB.Filters 9 { 10 /// <summary> 11 /// 身份(權限)過濾器 12 /// </summary> 13 public class MyAuthorizeAttribute:AuthorizeAttribute 14 { 15 /// <summary> 16 /// core操作上下文 17 /// </summary> 18 private ICoreSession iCoreSession; 19 20 #region 構造方法 傳入過濾器 21 public MyAuthorizeAttribute(ICoreSession iCoreSession) 22 { 23 this.iCoreSession = iCoreSession; 24 } 25 #endregion 26 27 #region 重寫權限驗證器 28 public override void OnAuthorization(AuthorizationContext filterContext) 29 { 30 //檢測是否貼有跳過標簽 31 if (!DoesSkip<Filters.SkipAttribute>(filterContext)) 32 { 33 //獲取區域名 34 string strArea = "AreaIsNull"; 35 if (filterContext.RouteData.DataTokens.Count > 0) 36 { 37 strArea = filterContext.RouteData.DataTokens["area"].ToString().ToLower(); 38 } 39 //獲取控制器名 40 string strController = filterContext.RouteData.Values["controller"].ToString().ToLower(); 41 //獲取方法名 42 string strAction = filterContext.RouteData.Values["action"].ToString().ToLower(); 43 44 //filterContext.HttpContext.Response.Write(strArea + "--" + strController + "--" + strAction); 45 OperateHelper.BussinessHelper h = new OperateHelper.BussinessHelper(iCoreSession); 46 //學生域之下 47 if (strArea == "student") 48 { 49 if (h.StudentSession == null) 50 { 51 filterContext.Result = new RedirectResult("/home/login"); 52 } 53 } 54 //教師域之下 55 else if (strArea == "teacher") 56 { 57 if (h.TeacherSession == null) 58 { 59 //如果在主頁控制器下 則直接跳轉 其他 提示過期 60 if (strController == "teacherhome") 61 { 62 //filterContext.HttpContext.Response.Redirect("/home/login"); 63 filterContext.Result = new RedirectResult("/home/login"); 64 } 65 else 66 { 67 filterContext.HttpContext.Response.Write("登陸已過期,請刷新頁面"); 68 filterContext.HttpContext.Response.End(); 69 } 70 71 } 72 else 73 { 74 //判斷是否對應權限 75 if (!CheckPermission(strController, h.TeacherSession.AuthorityList)) 76 { 77 filterContext.HttpContext.Response.Write("你的請求超出了你的權限,如修改過系統權限,請重新登錄系統"); 78 filterContext.HttpContext.Response.End(); 79 } 80 } 81 } 82 } 83 } 84 #endregion 85 86 #region 檢測是否貼有某標簽 + bool DoesSkip<T>(AuthorizationContext filterContext) where T : Attribute 87 /// <summary> 88 /// 檢測是否貼有某標簽 89 /// </summary> 90 /// <typeparam name="T">標簽type</typeparam> 91 /// <param name="filterContext">上下文</param> 92 /// <returns>是否貼有標簽</returns> 93 bool DoesSkip<T>(AuthorizationContext filterContext) where T : Attribute 94 { 95 if (!filterContext.ActionDescriptor.IsDefined(typeof(T), false) && !filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(T), false)) 96 { 97 return false; 98 } 99 return true; 100 } 101 #endregion 102 103 #region 檢驗訪問的管理員控制器是否與權限對應 + bool CheckPermission(string strController, List<WebModel.Authority> aul) 104 /// <summary> 105 /// 檢驗訪問的管理員控制器是否與權限對應 106 /// </summary> 107 /// <param name="filterContext">控制器名</param> 108 /// <param name="permission">權限列表</param> 109 /// <returns>是否有權限</returns> 110 private bool CheckPermission(string strController, List<WebModel.Authority> aul) 111 { 112 bool ret = false; 113 //將請求的控制器名與權限session中的控制器表進行匹配 如果有,則匹配通過 114 for (int i = 0; i < aul.Count(); i++) 115 { 116 if (aul[i].Controller.ToLower() == strController) 117 { 118 ret = true; 119 break; 120 } 121 } 122 return ret; 123 } 124 #endregion 125 } 126 }? MyAuthorizeAttribute繼承于AuthorizeAttribute,想要了解他的工作原理需要深入學習Asp.Net MVC的生命周期,網上已經有很多資料,在此就不贅述了。
86行的DoesSkip利用反射來判斷待檢測的控制器是否貼有跳過檢測的標簽,如有DoesSkip標簽則跳過檢測直接訪問。這是因為在Global中注冊了全局過濾器,如果對類似于登陸操作的控制器也執行權限驗證,這將是一個無限的死循環。所以需要在HelperController與HomeController等控制器上添上Skip標簽。
具體驗證功能是怎么實現的,參考代碼上的注釋。
3.統一管理Session與Cookie
也許你已經注意到了,在上面的過濾器中,存在一個BussinessHelper類,其實它的名字和它的功能并沒有很大的聯系,只是當初在整合時用了原先的部分代碼,而又沒有修改類名,這個類所實現的功能只是統一管理所有的Session與Cookie信息,并沒有其他復雜的業務上的操作。完整代碼如下
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Web; 5 using System.Web.SessionState; 6 7 8 namespace EDUA_WEB.OperateHelper 9 { 10 public class BussinessHelper 11 { 12 #region 對象保存名稱 13 /// <summary> 14 /// 驗證碼保存名 15 /// </summary> 16 const string VCODE = "vcode"; 17 /// <summary> 18 /// 教師信息保存名 19 /// </summary> 20 const string TEACHER_INFOKEY = "tinfo"; 21 /// <summary> 22 /// 學生信息保存名 23 /// </summary> 24 const string STUDENT_INFOKEY = "sinfo"; 25 #endregion 26 27 #region 構造方法 28 public BussinessHelper() { } 29 30 /// <summary> 31 /// 構造方法 32 /// </summary> 33 /// <param name="coreSession">業務操作對象</param> 34 public BussinessHelper(EDUA_ICore.ICoreSession coreSession) 35 { 36 this.iCoreSession = coreSession; 37 } 38 #endregion 39 40 #region http上下文的對象們 41 /// <summary> 42 /// 業務操作對象 43 /// </summary> 44 private EDUA_ICore.ICoreSession iCoreSession { get; set; } 45 46 /// <summary> 47 /// 當前Http上下文 48 /// </summary> 49 private HttpContext ContextHttp 50 { 51 get 52 { 53 return HttpContext.Current; 54 } 55 } 56 57 /// <summary> 58 /// session對象 59 /// </summary> 60 private HttpSessionState Session 61 { 62 get 63 { 64 return ContextHttp.Session; 65 } 66 } 67 68 /// <summary> 69 /// Cookie對象 70 /// </summary> 71 private HttpCookieCollection Cookies 72 { 73 get 74 { 75 return ContextHttp.Request.Cookies; 76 } 77 } 78 79 /// <summary> 80 /// Response 對象 81 /// </summary> 82 HttpResponse Response 83 { 84 get 85 { 86 return ContextHttp.Response; 87 } 88 } 89 #endregion 90 91 #region 驗證碼session對象 92 /// <summary> 93 /// 驗證碼session設置 94 /// </summary> 95 public string Vcode 96 { 97 get 98 { 99 if (Session[VCODE] == null) 100 return ""; 101 return Session[VCODE].ToString(); 102 } 103 set 104 { 105 Session[VCODE] = value; 106 } 107 } 108 #endregion 109 110 #region 學生對象session操作 111 /// <summary> 112 /// 學生類session 113 /// </summary> 114 public WebModel.Student StudentSession 115 { 116 get 117 { 118 WebModel.Student student = Session[STUDENT_INFOKEY] as WebModel.Student; 119 if (student == null) 120 { 121 string id = this.StudentSIDCookie; 122 if (id == "") 123 { 124 return null; 125 } 126 WebModel.ReturnVal rv = iCoreSession.IStudent.GetStudentBySID(id); 127 if (rv.Statu == WebModel.ReturnStatu.Success) 128 { 129 student = rv.Data as WebModel.Student; 130 //寫入session 131 Session[STUDENT_INFOKEY] = student; 132 } 133 else 134 { 135 return null; 136 } 137 138 } 139 return student; 140 } 141 set 142 { 143 Session[STUDENT_INFOKEY] = value; 144 } 145 } 146 #endregion 147 148 #region 學生對象cookie操作 149 public string StudentSIDCookie 150 { 151 get 152 { 153 if (Cookies[STUDENT_INFOKEY] == null) 154 { 155 return ""; 156 } 157 else 158 { 159 return EDUA_Util.EncrypHelper.DeEncryp(Cookies[STUDENT_INFOKEY].Value.ToString()); 160 } 161 } 162 set 163 { 164 HttpCookie cookie = new HttpCookie(STUDENT_INFOKEY, EDUA_Util.EncrypHelper.ToEncryp(value.ToString())); 165 cookie.Expires = DateTime.Now.AddHours(2); 166 Response.Cookies.Add(cookie); 167 } 168 } 169 #endregion 170 171 #region 教師對象Session操作 172 public WebModel.Teacher TeacherSession 173 { 174 get 175 { 176 WebModel.Teacher teacher = Session[TEACHER_INFOKEY] as WebModel.Teacher; 177 if (teacher == null) 178 { 179 string id = this.TeacherTIDCookie; 180 if (id == "") 181 { 182 return null; 183 } 184 WebModel.ReturnVal rv = iCoreSession.ITeacher.GetTeacherByID(id); 185 if (rv.Statu == WebModel.ReturnStatu.Success) 186 { 187 teacher = rv.Data as WebModel.Teacher; 188 Session[TEACHER_INFOKEY] = teacher; 189 } 190 else 191 { 192 return null; 193 } 194 } 195 return teacher; 196 } 197 set 198 { 199 Session[TEACHER_INFOKEY] = value; 200 } 201 } 202 #endregion 203 204 #region 教師對象Cookie操作 205 public string TeacherTIDCookie 206 { 207 get 208 { 209 if (Cookies[TEACHER_INFOKEY] == null) 210 { 211 return ""; 212 } 213 else 214 { 215 return EDUA_Util.EncrypHelper.DeEncryp(Cookies[TEACHER_INFOKEY].Value.ToString()); 216 } 217 } 218 set 219 { 220 HttpCookie cookie = new HttpCookie(TEACHER_INFOKEY, EDUA_Util.EncrypHelper.ToEncryp(value.ToString())); 221 cookie.Expires = DateTime.Now.AddHours(2); 222 Response.Cookies.Add(cookie); 223 } 224 } 225 #endregion 226 227 } 228 } View Code它的構造方法需要傳入核心類實例,因為當保存在客戶端的Session過期,而用戶又保存了Cookie的情況下,需要通過對加密的Cookie解密來產生新的session保存并返回。所以這里需要在Core中的teacher和student中添加對應的根據GUID返回教師(學生)實體的方法。
在前一篇登陸操作中,也使用了這個類,所要說明的是在teacher對象中,教師的權限列表在登陸時被取出放入了Session中,可以防止每一次驗證連接數據庫,減輕數據庫負擔。
4.生成對應權限菜單(樹)
這里我采用了EasyUI的異步加載來生成權限訪問列表,效果如下圖
在它的左側為其對應權限列表映射的權限樹。對應生成權限樹的控制器代碼如下
1 #region 1.1 生成左側菜單 2 public ActionResult GetMenuData() 3 { 4 OperateHelper.BussinessHelper h = new OperateHelper.BussinessHelper(); 5 List<WebModel.Authority> authList = h.TeacherSession.AuthorityList; 6 List<WebModel.EasyUIModel.TreeNode> tl = WebModel.EasyUIModel.TreeNode.getTreeNodeListByAuModelList(authList); 7 return Content(EDUA_Util.WEB.DataHelper.Obj2Json(tl)); 8 } 9 #endregion?
?
可以看到,它的權限列表也是從session中取出,并不需要再次連接數據庫。具體的easyui的代碼因為不涉及到框架本身,所以我就不提供了。
寫在最后
到這里,一個輕型框架已經可使用了。
最后說下關于分享源代碼的問題:
我當初寫這三篇博客的出發點:第一是為了理清我的思路,第二為將來可能接手系統的同學提供擴展開發時的參考,所以我并沒有把這些文章分享到博客園的首頁。但還是謝謝一些關注到這些文章的朋友。有一些朋友通過微博等問我能不能給下源代碼,其實大多數的核心的代碼在以上三篇文章中都已經給出了,如果仔細看完一定是能形成框架。至于完整的源代碼,因為已經存在不少的業務上的內容,重新整理需要不少時間,也不切實際,所以恕我不能打包上傳了,但還是謝謝大家的關注。如果有需要交流的,可以私信我的新浪微博@導彈林瀚,再次謝謝大家!
轉載請注明出處 huhuhuo的博客園
地址:http://www.cnblogs.com/linhan/p/4320862.html?
轉載于:https://www.cnblogs.com/linhan/p/4320862.html
總結
以上是生活随笔為你收集整理的2_MVC+EF+Autofac(dbfirst)轻型项目框架_用户权限验证的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 完美运动框架(js)
- 下一篇: 车载wifi人离开汽车,还有网络信号吗。