本章主要講解在MVC中靈活控制Action的訪問權限;
本章所使用的示例表也是上一張所使用的TbUser、TbRole、TbUserRole;
最終的效果是針對任意一個Action或Controller,都可以根據配置的角色來控制訪問權限;
完成此核心功能后,可以再往兩方面擴展常用功能:
1. 可以根據 組織/用戶/角色 的並集來控制權限
2. 以此核心功能為基礎,實現菜單的動態配置
PS:擴展功能本章暫時不講
文章提綱
-
概述
-
理論基礎
-
詳細步驟
-
總結
概述
一、MVC Form認證身份基礎
通常用法舉例:
1. web.config配置,開啟form認證

2. 需要認證的 Control或Action 上添加過濾,例如限制只有用戶"zhangsan"或者角色"管理員"可以訪問

另外還有一種常用形式表示:只要有用戶登錄就可以訪問
就是直接在Action或整個Controller上[Authorize]屬性過濾
二、為什么需要自定義MVC權限過濾器
上述解決方式中很明顯會發現有兩個缺點:
1. 修改權限時需在Action, Controller上修改后需重新編譯,不靈活。
2.過濾器中的Role是內置對象,如果不使用ASP.NET自身的集成權限方案,就無法按照角色來過濾。
解決這兩個問題,只需要擴展類AuthorizeAttribute即可。

理論基礎
為了能使用自定義的角色控制權限,我們需要擴展或繞過 ASP.NET 的Membership和Role provider 框架。
1.擴展:實現自定義的 Membership/Role provider
2.繞過:直接不使用
我們選擇繞過的方式,這樣的話更加靈活。
(因為如果你的角色結構和系統不一致,用擴展的方式弄起來比較麻煩)
我們使用form認證的三個核心API, 只用這幾個API既可以減少工作量,又可以和Membership/Role provider保持獨立。
1. FormsAuthentication.SetAuthCookie
用戶登錄后,指定用戶名
2. Request.IsAuthenticated
登錄后返回true
3. HttpContext.Current.User.Identity.Name
返回登錄的用戶名
權限過濾的完整過程:
1. Authetication ( 登錄 )
登錄成功后,調用 FormsAuthentication.SetAuthCookie 設置一個用戶名。
2. Authorization(授權)
新建自定義的授權屬性類:CustomAuthorizeAttribute(繼承於AuthorizeAtrribute),擴展權限過濾器
3. 類似於默認Authorize attribute的使用方法,附加自定義的authorize attribute到controller或action上去,實現權限過濾
詳細步驟
一、啟用form認證,完成登錄/退出 基本功能
1. 啟用 form 認證
配置web.config,啟用form認證

2. 完成登錄/退出 基本功能
新建Controller: AccountController.cs
public class AccountController : Controller { private MyDbContext db = new MyDbContext(); // GET: Account public ActionResult Index() { return View(); } public ActionResult Login() { ViewBag.LoginState = "登錄前..."; TempData["returnUrl"] = Request["ReturnUrl"]; //如果當前有登錄用戶,就需要跳轉到權限不足提示頁面 if (!string.IsNullOrEmpty(User.Identity.Name)) { return View("NoPermissions"); } else { return View(); } } [HttpPost] public ActionResult Login(TbUser user) { string url = Convert.ToString(TempData["returnUrl"]); var userinfo = db.TbUsers.FirstOrDefault(u => u.Email == user.Email && u.Password == user.Password); if (userinfo != null) { FormsAuthentication.SetAuthCookie(userinfo.UserName, false); if (!string.IsNullOrEmpty(url)) { ViewBag.LoginState = userinfo.UserName + "登錄后..."; return Redirect(url); } else { return Redirect("~/"); } } else { ViewBag.LoginState = user.Email + "用戶不存在..."; } return View(); } public ActionResult Logout() { FormsAuthentication.SignOut(); return Redirect(Request.UrlReferrer.ToString()); } /// <summary> /// 權限不足頁面 /// </summary> /// <returns></returns> public ActionResult NoPermissions() { return View(); } }
登錄功能:


登出功能:

這里對應的視圖文件就不展示了
二、准備好權限配置文件
1. 用到的基礎數據:用戶,角色及用戶/角色 關系

2. 角色與Action對應的權限關系
這里我們先用一個xml代替,理解這個后就可以擴展到DB中。
新建文件夾Config,新建ActionRoles文件,配置Action/Role的對應關系

PS:
Action未配置情況下,默認有訪問權限;
Action 配置角色為空,有訪問權限。
三、擴展 AuthorizeAttribute
1. 新建類CustomAuthorizeAttribute,繼承與AuthorizeAttribute
override兩個方法:
在請求授權時調用:

提供一個入口點用於自定義授權檢查,通過為true

CustomAuthorizeAttribute.cs(兩個方法的具體實現):
public class CustomAuthorizeAttribute : AuthorizeAttribute { private MyDbContext db = new MyDbContext(); /// <summary> /// 對應Action允許的角色 /// </summary> private string[] AuthRoles { get; set; } /// <summary> /// 在請求授權時調用 /// </summary> /// <param name="httpContext"></param> /// <returns></returns> protected override bool AuthorizeCore(HttpContextBase httpContext) { if (httpContext == null) { throw new ArgumentNullException("HttpContext"); } if (AuthRoles == null || AuthRoles.Length == 0) { return false; } #region 確定當前用戶角色是否屬於指定的角色 //獲取當前用戶所在角色 string sql = "select RoleName from TbRole where Id in(select roleId " + "from TbUserRole where userid = " + "(select id from TbUser where UserName=@UserName))"; string currentUser = httpContext.User.Identity.Name; SqlParameter[] paras = new SqlParameter[] { new SqlParameter("@UserName",currentUser) }; var userRoles = db.Database.SqlQuery<string>(sql, paras).ToList(); //驗證是否屬於對應角色 for (int i = 0; i < AuthRoles.Length; i++) { if (userRoles.Contains(AuthRoles[i])) { return true; } } #endregion return false; } /// <summary> /// 提供一個入口點用於自定義授權檢查,通過為true /// </summary> /// <param name="filterContext"></param> public override void OnAuthorization(AuthorizationContext filterContext) { string controllerName = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName; string actionName = filterContext.ActionDescriptor.ActionName; //獲取config文件配置的action允許的角色,以后可以轉到數據庫中 string roles = GetActionRoles(actionName, controllerName); if (!string.IsNullOrWhiteSpace(roles)) { this.AuthRoles = roles.Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries); } else { this.AuthRoles = new string[] { }; } base.OnAuthorization(filterContext); } /// <summary> /// 根據當前Controller和Action名稱獲取對應節點內容 /// </summary> /// <param name="action">Action名稱</param> /// <param name="controller">Controller名稱</param> /// <returns></returns> private static string GetActionRoles(string action, string controller) { XElement rootElement = XElement.Load(HttpContext.Current.Server.MapPath("~/Configs/") + "ActionRoles.xml"); XElement controllerElement = FindElementByAttribute(rootElement, "Controller", controller); if (controllerElement != null) { XElement actionElement = FindElementByAttribute(controllerElement, "Action", action); if (actionElement != null) { return actionElement.Value; } } return ""; } private static XElement FindElementByAttribute(XElement xelement, string tagName, string attribute) { XElement nowXelement = xelement.Elements(tagName).FirstOrDefault(e => e.Attribute("name").Value.Equals(attribute, StringComparison.OrdinalIgnoreCase)); return nowXelement; } }
總結
在此,權限控制的整個過程就差不多了,我們來測試一下。
新建HomeController, 新建一些Action做測試(Index, About,Install,Other)
回顧一下基礎數據。
HomeController.cs
namespace TestMVC.Controllers { [CustomAuthorizeAttribute] public class HomeController : Controller { // GET: Home public ActionResult Index() { return View(); } public ActionResult About() { return View(); } public ActionResult Install() { return View(); } public ActionResult Other() { return View(); } } }
對應視圖:
補充說明:
如下圖,可以設置為全局。
這樣就不需要單個設置,對所有Action應用自定義過濾條件。

