微軟在推出mvc框架不久,短短几年裏,版本更新之快,真是大快人心,微軟在這種優秀的框架上作了大量的精力投入,是值得贊同的,畢竟程序員駕馭在這種框架上,可以強力的精化代碼,代碼層次也更加優雅,擴展較爲方便,使之程序員把更多的精力投入到業務中來。程序員
不少時候我就在想,是否是該把傳統的用戶權限管理換個方式了呢?換成MVC AOP的思想權限控制,諸如controller action這種方案行的通嗎?答案是確定的,人的思想永遠是第一位!web
看看咱們想要達到的效果數據庫
1)權限列表 安全
2)菜單權限列表cookie
3)角色權限列表mvc
4)用戶權限列表框架
5)主從菜單的配置佈局
6)圖標的自由定製網站
1.基於controller action控制權限的好處ui
其實合起來看,controller與action即控制了一個頁面的行爲,如能是否查看,寫入,修改權限,而咱們在開發的過程當中,這些方法都已完成,這樣省去了像傳統方式對每一個頁面從新控制生成的步驟。當前經過反射程序集收集全部的controller action信息,自動化收集權限控制是比較可觀的。
2.基於controller action控制權限的方案
權限列表經過反射自動從程序集獲取,管理員(默認有最高權限)把權限分配給用戶及分配權限分配給角色,管理員對用戶分配權限角色權限,如圖所示
用戶權限列表及角色權限列表分配成形Controller與Action信息後,經過代碼控制對應的控制器及方法是否有權限。
3.基於controller action控制權限信息的提取的方案
有些方法不須要提取的,有些方法須要權限控制,爲了讓程序方便提取權限信息,咱們加入特性,若是方法或控制器有此特性,即要控制的,固然爲了節約代碼,默認特性是false,這樣,沒有加特性的或者特性是false的,都不用提取!
4.基於mvc controller和action 權限管理流程圖
不明白的親們不用太着急,下面開始詳細的步驟吧!
首先咱們經過上面的分析,咱們用模型來一點一點的剖析
從左至右,相關的模型是,權限信息列表,角色列表,用戶信息列表,部門列表,菜單列表
1)一個用戶能夠有多個權限,一個權限能夠分配多個用戶,因此是多對多的關係
2)一個角色能夠有多個權限,一個權限能夠分配多個角色,因此是多對多的關係
3)一個用戶能夠有多個角色,一個角色能夠分配多個用戶,因此是多對多的關係
4)一個用戶能夠有多個部門,一個部門能夠分配多個角戶,因此是多對多的關係
5)菜單列表,主要針對後臺每一個用戶或角色不一樣的展現方式,以及能夠自定義圖片等
好了,到此爲止,咱們開始正式的工做。
由上列模型,咱們採用code first生成數據庫,如何使用 code first 請搜索下百度或未來有專門的章節介紹,這裏再也不累贅說明!
咱們添加,添加之後幾個model類
1)權限控制類:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ComponentModel; using System.ComponentModel.DataAnnotations; namespace WL.Models.Permission { [DisplayName("控制權限")] public class ActionPermission { [Key] [DisplayName("控制權限ID")] public int ActionPermissonID { set; get; } [DisplayName("控制權限名稱")] public string ActionPermissionName { set; get; } [DisplayName("控制器權限名稱")] public string ControllerPermissionName { set; get; } [DisplayName("說明")] public string Description { set; get; } [DisplayName("建立時間")] public DateTime CreateDate { set; get; } [DisplayName("操做用戶名")] public string Operator { set; get; } [DisplayName("最後修改時間")] public DateTime LateDate { set; get; } [DisplayName("圖標")] public string Icon { get; set; } [DisplayName("狀態")] public int State { set; get; } [Description("用戶實體集合")] public virtual ICollection<User> UserCollection { get; set; } [Description("角色實體")] public virtual ICollection<Role> RoleCollection { get; set; } } }
2)部門類:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ComponentModel; using System.ComponentModel.DataAnnotations; namespace WL.Models.Permission { [DisplayName("部門")] [DisplayColumn("DepartMentID")] public class DepartMent { [Key] [DisplayName("部門ID")] public int DepartMentID { set; get; } [DisplayName("部門名稱")] public string DepartName { set; get; } [DisplayName("說明")] public string Description { set; get; } [DisplayName("建立時間")] public DateTime CreateDate { set; get; } [DisplayName("操做用戶名")] public string Operator { set; get; } [DisplayName("最後修改時間")] public DateTime LateDate { set; get; } [DisplayName("圖標")] public string Icon { get; set; } [DisplayName("狀態")] public int State { set; get; } [Description("用戶實體集合")] public virtual ICollection<User> UserCollection { get; set; } } }
3)菜單類:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ComponentModel; using System.ComponentModel.DataAnnotations; namespace WL.Models.Permission { [DisplayName("菜單")] [DisplayColumn("MenuID")] public class Menu { [Key] [DisplayName("菜單ID")] public int MenuID { set; get; } [DisplayName("ParentID")] public int ParentsID { set; get; } [DisplayName("菜單名稱")] public string MenuName { set; get; } [DisplayName("菜單圖標")] public string MenuIco { set; get; } [DisplayName("說明")] public string Descriptotion { set; get; } [DisplayName("控制權限名稱")] public string ActionPermissionName { set; get; } [DisplayName("控制器權限名稱")] public string ControllerPermissionName { set; get; } [DisplayName("連接地址")] public string Url { set; get; } [DisplayName("排序")] public string Sort { set; get; } [DisplayName("建立時間")] public DateTime CreateDate { set; get; } [DisplayName("操做用戶名")] public string Operator { set; get; } [DisplayName("最後修改時間")] public DateTime LateDate { set; get; } [DisplayName("圖標")] public string Icon { get; set; } [DisplayName("狀態")] public int State { set; get; } } }
4)角色類
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ComponentModel.DataAnnotations; using System.ComponentModel; namespace WL.Models.Permission { [DisplayName("角色成員")] [DisplayColumn("RoleID")] public class Role { [Key] [DisplayName("用戶ID")] public int RoleID { set; get; } [DisplayName("角色名")] public string RoleName { set; get; } [DisplayName("說明")] public string Description { set; get; } [DisplayName("建立時間")] public DateTime CreateDate { set; get; } [DisplayName("操做用戶名")] public string Operator { set; get; } [DisplayName("最後修改時間")] public DateTime LateDate { set; get; } [DisplayName("圖標")] public string Icon { get; set; } [DisplayName("狀態")] public int State { set; get; } [Description("用戶實體集合")] public virtual ICollection<User> UserCollection { get; set; } [Description("控制權限實體集合")] public virtual ICollection<ActionPermission> ActionPermissionCollection { get; set; } } }
5)用戶後臺管理員類
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ComponentModel.DataAnnotations; using System.ComponentModel; namespace WL.Models.Permission { [DisplayName("用戶成員")] [DisplayColumn("UserId")] public class User { [Key] [DisplayName("用戶ID")] public int UserID { set; get; } [DisplayName("用戶名稱")] public string UserName { set; get; } [DisplayName("用戶密碼")] public string PassWord { set; get; } [DisplayName("用戶郵件")] public string Mail { set; get; } [DisplayName("用戶電話")] public string Phone { set; get; } [DisplayName("說明")] public string Description { set; get; } [DisplayName("建立時間")] public DateTime CreateDate { set; get; } [DisplayName("操做用戶名")] public string Operator { set; get; } [DisplayName("最後修改時間")] public DateTime LateDate { set; get; } [DisplayName("圖標")] public string Icon { get; set; } [DisplayName("狀態")] public int State { set; get; } [Description("部門實體")] public virtual ICollection<DepartMent> DepartMentCollection { get; set; } [Description("角色實體")] public virtual ICollection<Role> RoleCollection { get; set; } [Description("控制權限實體集合")] public virtual ICollection<ActionPermission> ActionPermissionCollection { get; set; } } }
在項目中任意添加一個項目 MvsApp->PermissionContext
並配置web.config
在此項目中設爲啓始項目 而後臺下操做
設置默認項目
在命令行裏輸入
Enable-Migrations-->Add-Migration Rating-->update-database
在數據中將會生成權限數據庫
咱們採用code first好處在於,「EF」自動爲咱們建立中間關係表,省去了中間咱們手工建立的省麻煩。若是不清楚的朋友,請查EF Code first 自動遷移相關文章。
而後,咱們佈局各個控制器以下圖
能夠看出,任何一個權限控制器類都所繼續一個基類baseController,這樣寫的好處只有一個,節約代碼,由於‘懶’。
在基baseControlle類中咱們將爲權限控制進行描述。
基於AOP的思想,在此基類baseControlle中,重寫 Controller的 OnActionExecuting()方法,來判斷用戶的相關權限,大體邏緝流程以下
經過上面的流程的分析,代碼以下
1)判斷用否是否成功登陸
#region -----校驗用戶是否登陸進入網站的----- base.OnActionExecuting(filterContext); HttpCookie UserInfo = System.Web.HttpContext.Current.Request.Cookies.Get("COOKIE_NAME_FOR_USER"); //檢驗用戶是否已經登陸,若是登陸則不執行,不然則執行下面的跳轉代碼 if (UserInfo == null) { filterContext.Result = RedirectToRoute(new { Controller = "Login", Action = "Index" }); return; } #endregion var UserInfoStr = UserInfo["COOKIE_NAME_FOR_USER_INFO"].ToString(); if (string.IsNullOrEmpty(UserInfoStr)) { filterContext.Result = RedirectToRoute(new { Controller = "Login", Action = "Index" }); return; }
2)提取登陸用戶相關登陸的信息(註冊這裏是後臺相關演示,安全性沒作處理)
var userNameArray = UserInfoStr.Split(new string[] { "$|$" }, StringSplitOptions.RemoveEmptyEntries); var username = userNameArray[0]; var pwdword = userNameArray[1]; CurrentUserInfo = this.userInfoService.LoadEntites(u => username.Equals(u.UserName) && pwdword.ToLower().Equals(u.PassWord.ToLower()) && u.State == 0).FirstOrDefault(); if (CurrentUserInfo == null) { filterContext.Result = RedirectToRoute(new { Controller = "Login", Action = "Index" }); return; }
3)獲取當前用戶所在的控制器與方法並根據權限特性(上文提到的自定義判斷權限依據)判斷當前控制器是否須要權限控制
//先將當前的請求,到權限表裏面去找對應的數據 System.Web.Routing.RouteData Rotedate = filterContext.RequestContext.RouteData; string controller = (RouteData.Values["controller"] ?? "").ToString().ToLower(); string action = (RouteData.Values["action"] ?? "").ToString().ToLower(); //默認 if (HasDefaultAction(filterContext)) return;
private bool HasDefaultAction(ActionExecutingContext filterContext) { if (CurrentUserInfo.UserName == "admin") return true; Type t = filterContext.ActionDescriptor.ControllerDescriptor.ControllerType; string actionname = filterContext.RouteData.Values["action"].ToString(); //默認沒用應用權限特性,僅不須要權限的才進行控件該特性,若是應用,HasPermission必須false才能夠進行放行些權限 object[] astri = GetPermissionAttribute<PermissionAttribute>(actionname, t); if (astri.Length > 0) { //更具自定義的特性獲得須要調用的類名與方法名 PermissionAttribute u = astri[0] as PermissionAttribute; return !u.RequiredPermission; } return false; }
4)至此用戶程序都經過後,開始用戶相關的權限判斷。有兩條線路判斷,一是角戶權限判斷,二是用戶權限判斷。
角戶權限判斷
//而後和權限表進行對比,若是取出來則經過請求,不然不經過 //取出當前權限的數據 //想去用戶權限表裏面查詢有沒有數據 //分析線路 User->Role->Action //拿到當前的用戶信息 var userCurrent = userInfoService.LoadEntites(u => u.UserID == CurrentUserInfo.UserID).FirstOrDefault(); var role = (from r in userCurrent.RoleCollection from c in r.ActionPermissionCollection where c.ActionPermissionName.ToLower() == action && c.ControllerPermissionName.ToLower() == controller && c.State == 0 select r).FirstOrDefault(); if (role != null) return;
用戶權限(後臺用戶)判斷
//分析線路 User->Action var user = (from c in userCurrent.ActionPermissionCollection where c.ActionPermissionName.ToLower() == action && c.ControllerPermissionName.ToLower() == controller && c.State == 0 select c).FirstOrDefault(); if (user != null) return;
其它可能的錯誤
public void EndRequest(ActionExecutingContext filterContext) { filterContext.Result = RedirectToRoute(new { Controller = "Home", Action = "Error" }); }
至此,一個基本的用戶控制已經完成
完整的代碼:
#region -----校驗用戶是否登陸進入網站的----- base.OnActionExecuting(filterContext); HttpCookie UserInfo = System.Web.HttpContext.Current.Request.Cookies.Get("COOKIE_NAME_FOR_USER"); //檢驗用戶是否已經登陸,若是登陸則不執行,不然則執行下面的跳轉代碼 if (UserInfo == null) { filterContext.Result = RedirectToRoute(new { Controller = "Login", Action = "Index" }); return; } #endregion var UserInfoStr = UserInfo["COOKIE_NAME_FOR_USER_INFO"].ToString(); if (string.IsNullOrEmpty(UserInfoStr)) { filterContext.Result = RedirectToRoute(new { Controller = "Login", Action = "Index" }); return; } var userNameArray = UserInfoStr.Split(new string[] { "$|$" }, StringSplitOptions.RemoveEmptyEntries); var username = userNameArray[0]; var pwdword = userNameArray[1]; CurrentUserInfo = this.userInfoService.LoadEntites(u => username.Equals(u.UserName) && pwdword.ToLower().Equals(u.PassWord.ToLower()) && u.State == 0).FirstOrDefault(); if (CurrentUserInfo == null) { filterContext.Result = RedirectToRoute(new { Controller = "Login", Action = "Index" }); return; } #region -------檢驗用戶是否有訪問此地址的權利---- //先將當前的請求,到權限表裏面去找對應的數據 System.Web.Routing.RouteData Rotedate = filterContext.RequestContext.RouteData; string controller = (RouteData.Values["controller"] ?? "").ToString().ToLower(); string action = (RouteData.Values["action"] ?? "").ToString().ToLower(); //默認 if (HasDefaultAction(filterContext)) return; //而後和權限表進行對比,若是取出來則經過請求,不然不經過 //取出當前權限的數據 //想去用戶權限表裏面查詢有沒有數據 //分析線路 User->Role->Action //拿到當前的用戶信息 var userCurrent = userInfoService.LoadEntites(u => u.UserID == CurrentUserInfo.UserID).FirstOrDefault(); var role = (from r in userCurrent.RoleCollection from c in r.ActionPermissionCollection where c.ActionPermissionName.ToLower() == action && c.ControllerPermissionName.ToLower() == controller && c.State == 0 select r).FirstOrDefault(); if (role != null) return; //分析線路 User->Action var user = (from c in userCurrent.ActionPermissionCollection where c.ActionPermissionName.ToLower() == action && c.ControllerPermissionName.ToLower() == controller && c.State == 0 select c).FirstOrDefault(); if (user != null) return; EndRequest(filterContext);
到此,能夠看到任何一個控制器都將受到這個基控制器的‘影響’,爲此咱們不必去一個一個的控制器類實現,AOP的思想,確實爲咱們節約了很多代碼。
簡單的總結下思路:
用戶登陸後寫入cookie ,在此基類中,讀取此cookie存儲的相關用戶相關信息,如用戶名等,同時讀取當前訪問的mvc相關的controller action用戶和角色相關的權限信息,
若是用戶有此controller action權利,那麼咱們便可放行
爲了節約數據庫的資源,在此基礎加入判斷,當前訪問的controller action 與用戶當前操做的controller action 是否控制特性判斷,若是在些方法上不存在權限控制的特性或者爲false,那麼,說明不須要權限控制,直接放行。
原本想在一篇文章寫完全部的權限內容的,寫到此,文章太夠長了,只能期待下篇。
下篇文章咱們主要寫各類控制器的具體控制,文章很長,請細細品嚐哦。