以前一直沒怎麼接觸過權限驗證這塊,恰好公司老平臺改版,就有了這篇權限驗證。此篇文章大體講解下 精確到按鈕級別的驗證如何實現、以及權限驗證設計的參考思路(菜鳥一枚,大神勿噴)。數據庫
在開發大項目的時候總會有相關的AOP面向切面編程的組件,而MVC(特指:Asp.Net MVC,如下皆同)項目中不想讓MVC開發人員去關心和寫相似身份驗證,日誌,異常,行爲截取等這部分重複的代碼,那咱們能夠經過AOP截取實現,而在MVC項目中咱們就能夠直接使用它提供的Filter的特性幫咱們解決,不用本身實現複雜的AOP了。編程
在Asp.NET Mvc中當你有如下及相似如下需求時你可使用Filter(過濾器)功能瀏覽器
Asp.Net MVC提供瞭如下幾種默認的Filter:緩存
你們注意一點,Asp.Net MVC提供的ActionFilterAttribute默認實現了IActionFilter和IResultFilter。而ActionFilterAttribute是一個Abstract的類型,因此不能直接使用,由於它不能實例化,因此咱們想使用它必須繼承一下它而後才能使用。服務器
Filter繼承於ActionFilterAttribute抽象類,並能夠覆寫 void OnActionExecuting(ActionExecutingContext) voidOnActionExecuted(ActionExecutedContext) void OnResultExecuting(ResultExecutingContext) void OnResultExecuted(ResultExecutedContext)。app
它們的執行前後順序以下:ide
OnActionExecuting是Action執行前的操做post
OnActionExecuted則是Action執行後的操做字體
OnResultExecuting是解析ActionResult前執行this
OnResultExecuted是解析ActionResult後執行
接下來咱們只要對以上的方法進行重寫就能夠在相應的步驟作一些操做了。
光說不練假把式,下面我給你們一個示例,來看看它們的執行順序,首先添加一個普通的類TestFilterAttribute,這個類要繼承ActionFilterAttribute,代碼以下:
1 /// <summary> 2 /// Filter 執行順序Test,須要繼承篩選器的基類ActionFilterAttribute 3 /// </summary> 4 public class TestFilterAttribute : ActionFilterAttribute 5 { 6 public string Message { get; set; } 7 8 public override void OnActionExecuting(ActionExecutingContext filterContext) 9 { 10 base.OnActionExecuting(filterContext); 11 filterContext.HttpContext.Response.Write("Action執行以前" + Message + "<br />"); 12 } 13 14 public override void OnActionExecuted(ActionExecutedContext filterContext) 15 { 16 base.OnActionExecuted(filterContext); 17 filterContext.HttpContext.Response.Write("Action執行以後" + Message + "<br />"); 18 } 19 20 public override void OnResultExecuting(ResultExecutingContext filterContext) 21 { 22 base.OnResultExecuting(filterContext); 23 filterContext.HttpContext.Response.Write("返回Result以前" + Message + "<br />"); 24 } 25 26 public override void OnResultExecuted(ResultExecutedContext filterContext) 27 { 28 base.OnResultExecuted(filterContext); 29 filterContext.HttpContext.Response.Write("返回Result以後" + Message + "<br />"); 30 } 31 }
寫完這個代碼後,咱們回到Action上,打上上面的標記以下所示:
1 [TestFilterAttribute(Message = "Action")] 2 public ActionResult Index() 3 { 4 HttpContext.Response.Write("Action正在執行···<br />"); 5 return Content("正在返回Result···<br />"); 6 }
而後經過瀏覽器訪問上面的Action即可以看到下面的執行順序
總的執行順序是:
Action執行前:OnActionExecuting方法先執行→Action執行→OnActionExecuted方法執行→OnResultExecuting方法執行→返回的ActionRsult中的executeResult方法執行→OnResultExecuted執行
最終顯示的效果就是如上圖所示。
感受還不錯吧!哈哈!這要想用到這個過濾機制的地方的時候,只要在Action上面添加標記即可以實現效果。
若是咱們將此標籤打到Controller上的話,TestFilterAttributeFilter將做用到Controller下的全部的Action。例如以下代碼所示:
[TestFilter(Message="Controller")] public class TestFilterController : Controller { // // GET: /TestFilter/ [TestFilter(Message="Action")] public ActionResult Index() { HttpContext.Response.Write("Action正在執行···<br />"); return Content("正在返回Result···<br />"); } }
若是單純的按照上面的代碼來作就有個問題了,咱們再執行顯示的頁面會有什麼狀況呢?Controller上的Filter會執行嗎?那標籤的做用會執行兩次嗎?下面是最後的執行結果以下圖所示:
結果說明
默認狀況下Action上打了TestFilterAttribute 標籤後,雖然在Controller上也打上了此標籤,但它只有Action上的標籤起做用了。
補充:若是Action沒有打上TestFilterAttribute標籤,那麼Controller上的標籤便會被執行。
Index 執行時,Filter的方法只執行了一次,而某些狀況下咱們也想讓Controller上的FilterAttribute也執行一次TestFilterAttribute,那咱們怎麼才能讓Controller上的[TestFilter(Message = "controller")]也起做用呢?
答案是:咱們只需在TestFilterAttribute類的定義上打上標記[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]便可【下面類的最上面紅色字體部分】,也就是讓其成爲能夠屢次執行的Action。代碼以下:
/// <summary> /// Filter 執行順序Test,須要繼承篩選器的基類ActionFilterAttribute /// </summary [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] //打標記是讓全部的Controller的標記生效 public class TestFilterAttribute : ActionFilterAttribute { public string Message { get; set; } public override void OnActionExecuting(ActionExecutingContext filterContext) { base.OnActionExecuting(filterContext); filterContext.HttpContext.Response.Write("Action執行以前" + Message + "<br />"); } public override void OnActionExecuted(ActionExecutedContext filterContext) { base.OnActionExecuted(filterContext); filterContext.HttpContext.Response.Write("Action執行以後" + Message + "<br />"); } public override void OnResultExecuting(ResultExecutingContext filterContext) { base.OnResultExecuting(filterContext); filterContext.HttpContext.Response.Write("返回Result以前" + Message + "<br />"); } public override void OnResultExecuted(ResultExecutedContext filterContext) { base.OnResultExecuted(filterContext); filterContext.HttpContext.Response.Write("返回Result以後" + Message + "<br />"); } }
瀏覽效果以下圖:
咱們看到的結果是Controller上的ActionFilter先於Action上打的標記執行。一樣Result執行executeResult方法以前也是先執行Controller上的Filter標記中的OnResultexecuteing方法。
最後的執行順序是:Controller上的OnActionExecuting→Action上的OnActionExecuting→Action執行→Action上的OnActionExecuted→Controller上的OnActionExecuted
到此Action就執行完畢了,咱們看到是一個入棧出棧的順序。後面是Action返回ActionResult後執行了ExecuteResult方法,但在執行以前要執行Filter。具體順序爲:
接上面→Controller的OnResultExecuting方法→Action上的OnResultExecuting→Action返回ActionResult後執行了ExecuteResult方法→Action上的OnResultExecuted執行→Controller上的OnResultExecuted執行→結束。
又接着一個問題也來了,咱們想有些公共的方法須要每一個Action都執行如下,而在全部的Controller打標記是很痛苦的。幸虧Asp。Net MVC3帶來了一個美好的東西,全局Filter。而怎麼註冊全局Filter呢?答案就在Global.asax中。讓咱們看如下代碼,我是如何將上面咱們定義的TestFilterAttribute 註冊到全局Filter中。上代碼:
public static void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(new HandleErrorAttribute()); //註冊全局過濾器 filters.Add(new TestFilterAttribute() { Message="全局"}); }
效果以下圖:
咱們看到的結果是全局的Action首先執行,而後纔是Controller下的Filter執行,最後纔是Action上的標籤執行。固然這是在TestFilterAttribute類的定義上打上標記[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]的前提下。否則 若是Action打上了標籤跟Controller的相同則它只會執行Action上的Filter。
規定頁面的訪問形式,如
[AcceptVerbs(HttpVerbs.Post)] public ActionResult Example(){ return View(); }
頁面只能以Post形式訪問,即表單提交。
規定Action的名稱。
應用場景:若是不想用方法名作爲Action名,或Action名爲關鍵字的話,如
[ActionName("class")] public ActionResult Example(){ return View(); }
當前方法僅是普通方法不解析爲Action
爲Action添加緩存
[OutputCache(Duration = 60, VaryByParam = "*")] public ActionResult Example() { return View(); }
該Action能夠接受Html等危險代碼(ASP.NET MVC在aspx中設置<%@ Page 的屬性沒法完成等同任務。)
[ValidateInput(false)] public ActionResult Example() { return View(); }
用於驗證服務器篡改。
[ValidateAntiForgeryToken] public ActionResult Example() { return View(); }
通過這一篇文章的介紹咱們大致瞭解了Filter的使用方法,還了解到全局Filter的用法,尤爲是當相同的Filter重複做用到同一個Action上時,若是沒有設置可屢次執行的標籤那只有Action上的Filter執行,而Controller和全局Filter都被屏蔽掉,可是設置可屢次執行,那首先執行全局Filter其次是Controller再次之就是Action上的Filter了。同時還了解了系統的Filter的用法。
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
萬惡的分割線
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
咱們能夠先來看看它們之間的關係
.....
可能你們看這個數據字典還不夠直觀那咱們來看看下圖描述的:
整體上分爲:組織權限管理、管理員管理、功能菜單管理、管理組、菜單動做管理幾部分。
注意:由於組和角色都具備上下級關係,因此下級的組或角色的權限只能在本身的直屬上級的權限中選擇,下級的組或者角色的總的權限都不能大於直屬上級的總權限。
新建權限實體
public class UsersRolesOper { /// <summary> /// 權限ID /// </summary> public int RoleID { get; set; } /// <summary> /// 菜單名稱 /// </summary> public string MenuName { get; set; } /// <summary> /// 菜單code /// </summary> public string MenuCode { get; set; } /// <summary> /// 菜單ID /// </summary> public int MenuID { get; set; } /// <summary> /// 動做ID /// </summary> public int ActionID { get; set; } /// <summary> /// 動做名稱 /// </summary> public string ActionName { get; set; } /// <summary> /// 動做Code /// </summary> public string ActionCode { get; set; } }
新建過濾器類AuthorityCheck 實現權限驗證
public class AuthorityCheck : ActionFilterAttribute { private readonly IRolesService _roles;public string KeyWork { get; set; } public string MoudleName { get; set; } public bool Menu_button { get; set; } public override void OnActionExecuting(ActionExecutingContext filterContext) { //以前執行 HttpResponseBase response = filterContext.HttpContext.Response; if (_roles.AuthorityCheck(KeyWork, HttpContext.Current.User.Identity.Name, MoudleName,Menu_button)) { filterContext.Controller.ViewBag.isPersimion = true; } else { filterContext.Controller.ViewBag.isPersimion = false; } base.OnActionExecuting(filterContext); } }
/// <summary> /// 驗證權限 /// </summary> /// <param name="keyWork">傳入code</param> /// <param name="userName">用戶</param> /// <param name="ModuleName">菜單或按鈕名稱</param> /// <param name="Menu_button">菜單仍是按鈕,false是菜單</param> /// <returns></returns> public bool AuthorityCheck(string keyWork, string userName, string ModuleName,bool Menu_button=false) { List<UsersRolesOper> userRoleOper = GetUserPermission(userName, ModuleName); if (userRoleOper != null) { foreach (UsersRolesOper item in userRoleOper) { if (Menu_button) { if (item.ActionCode.ToLower() == keyWork.ToLower()) { return true; } } else { if (item.ActionName == "查看") { if (item.MenuCode.ToLower() == keyWork.ToLower()) { return true; } } } } return false; } else { return false; } }
GetUserPermission方法就不貼了,主要是從數據庫讀取用戶權限,存放在List集合中
若是Action是返回的Json,能夠返回Json後再前臺處理,其中ViewBag.isPersimion對應過濾器類中返回的結果。例如:
/// <summary> /// 編輯用戶保存全部資料 /// </summary> /// <param name="model"></param> /// <param name="type"></param> /// <returns></returns> [HttpPost] [AuthorityCheck(KeyWork = "Modify", MoudleName = "修改",Menu_button=true)] public ActionResult AccountEdit(GoldEditeView model, int type = 0) { ResponseData res = new ResponseData { Status = ResponseStatu.ERROR, Message = "保存出錯" }; if (!LoginInfo.IsSA) { if (!ViewBag.isPersimion) { res = new ResponseData { Status = ResponseStatu.LIMIT, Message = "你沒有權限進行此操做!" }; return MyJson(res); } } try { bool flag = false; if (model.Person.ID == 0) { } else { model.PersonInfo.ModifiedBy = LoginInfo.UserName; flag = _accService.UpdateGoldPerson(model); } if (flag) res = new ResponseData { Status = ResponseStatu.SUCCESS, Message = "SUCCESS", Data = model.Person.ID }; } catch (Exception ex) { res = new ResponseData { Status = ResponseStatu.SUCCESS, Message = "保存出錯", Data = ex.Message }; } return MyJson(res); }
若是是window.location.href方式的Action,能夠這樣處理:
[HttpPost] [AuthorityCheck(KeyWork = "Export", MoudleName = "導出",Menu_button=true)] public ActionResult DoneExport(DoneDataSearchView entity = null) { if (!ViewBag.isPersimion) { return Content("<script>alert('您沒有操做權限');location.href='/GoldAccounts/DoneList'</script>"); } try { byte[] file = this._accService.GetDoneExportDT(entity); if (file == null) return Redirect("/GoldAccounts/DoneList"); else return File(file, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "GoldAccounts_Excel" + DateTime.Now.ToString("yyyyMMddHHmm") + ".xls"); } catch (Exception ex) { throw ex; } }
最後,若是菜單沒有權限,能夠直接跳轉到另外一個提示頁面:
[AuthorityCheck(KeyWork = "paper_account", MoudleName = "開戶受理", Menu_button = false)] public ActionResult PendingList(string id) { try { if (!ViewBag.isPersimion) { return Redirect("/Home/Limit"); } return View(); } catch (Exception ex) { throw ex; } }
最後附一張效果圖:
....