一、引言
我們都知道ASP.net mvc權限控制都是實現AuthorizeAttribute類的OnAuthorization方法。
下面是最常見的實現方式:
public class CustomAuthorizeAttribute : AuthorizeAttribute { public override void OnAuthorization(AuthorizationContext filterContext) { if (!filterContext.RequestContext.HttpContext.Request.IsAuthenticated) { filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new { controller = "account", action = "login", returnUrl = filterContext.HttpContext.Request.Url, returnMessage = "您無權查看." })); return; } base.OnAuthorization(filterContext); } }
然后在需要驗證的Action上打上[CustomAuthorize]標簽就可以了。
這種方式是比較粗粒度的解決方案,由於是已經將定義好(約定好的)權限hard code帶對應的Action上,所以無法實現用戶自定義權限控制。
看一下代碼:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; namespace Deepleo.Role.Controllers { public class UserController : Controller { [UserAuthorize] public ActionResult Index() { return View(); } [AdminAuthorize] public ActionResult Admin() { return View(); } [UserAuthorize] public ActionResult Detail() { return View(); } } }
我們有一個UserController,他有3個Action:Index,Admin,Detail.其中Admin需要系統管理員權限,其他兩個值需要User權限。這樣就需要建立AdminAuthorizeAttribute和UserAuthorizeAttribute.這樣做就無法實現用戶自定義權限。
二、基於角色的權限控制系統
基於角色的權限控制系統RBAC(Role Based Access Control)是目前最流行,也是最通用的權限控制系統。
對於ASP.NET MVC來說,這套系統很容易實現:Controller下的每一個Action可以看作是一個權限,角色就相當於多個權限的組合。
然后我們新建一個RoleAuthorizeAttribute,即對角色的屬性描述。
2.1 如何鑒權
這個RoleAuthorizeAttribute的關鍵在於如何拿到ControllerName和ActionName,查閱msdn其實很容易就實現了,不多說,直接上代碼:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Security; using System.Web.Mvc; using System.Web.Routing; using Deepleo.Role.Services; namespace Deepleo.Role.Attributes { public class RoleAuthorizeAttribute : AuthorizeAttribute { public override void OnAuthorization(AuthorizationContext filterContext) { var isAuth = false; if (!filterContext.RequestContext.HttpContext.Request.IsAuthenticated) { isAuth = false; } else { if (filterContext.RequestContext.HttpContext.User.Identity != null) { var roleService = new RoleService(); var actionDescriptor = filterContext.ActionDescriptor; var controllerDescriptor = actionDescriptor.ControllerDescriptor; var controller = controllerDescriptor.ControllerName; var action = actionDescriptor.ActionName; var ticket = (filterContext.RequestContext.HttpContext.User.Identity as FormsIdentity).Ticket; var role = roleService.GetById(ticket.Version); if (role != null) { isAuth = role.Permissions.Any(x => x.Permission.Controller.ToLower() == controller.ToLower() && x.Permission.Action.ToLower() == action.ToLower()); } } } if (!isAuth) { filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new { controller = "account", action = "login", returnUrl = filterContext.HttpContext.Request.Url, returnMessage = "您無權查看." })); return; } else { base.OnAuthorization(filterContext); } } } }
注意:這里用Ticket的Version存儲RoleId(最好不要這樣,原因看我的另一篇博文:http://www.cnblogs.com/deepleo/p/iso_cookies_formsAuthenticationTicket_version.html)。你也可以用其他方式。
主要是用到了 filterContext.ActionDescriptor和filterContext.ActionDescriptor。
2.2 如何生成權限控制列表
前面的role.Permissions的集合已經是定義好的權限列表。
Permissions類的定義如下:

using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Deepleo.Role.Entities { public class PermissionDefinition { public virtual int Id { set; get; } public virtual int ActionNo { set; get; } public virtual int ControllerNo { set; get; } public virtual string Name { set; get; } public virtual string ControllerName { set; get; } public virtual string Controller { set; get; } public virtual string Action { set; get; } public virtual DateTime AddDate { set; get; } } }
屬性Controller和Action記錄的是權限,ControllerName和ActionName用於顯示UI,ControllerNo和ActionNo用於顯示順序控制。
這里你可以手工將所有Action錄入數據庫中,然后實現RolService即可。但是顯然這種方法實在是太笨了,我們其實可以用反射+Attribute機制實現自動化載入權限控制表。原理很簡單,我就直接上關鍵代碼了。
以下是反射的代碼:

using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using Deepleo.Role.Services; using Deepleo.Role.Attributes; using Deepleo.Role.Entities; namespace Deepleo.Role.Controllers { public class InstallController : Controller { public ActionResult Index() { return View(); } [HttpPost] public ActionResult Index() { try { var roleService = new RoleService(); #region init permission createPermission(new UserController()); #endregion var allDefinedPermissions = roleService.GetDefinedPermissions(); #region 超級管理員角色初始化 var adminPermissions = new List<RolePermissionInfo>(); foreach (var d in allDefinedPermissions) { adminPermissions.Add(new RolePermissionInfo { AddDate = DateTime.Now, Permission = d, }); } int adminRoleId = roleService.AddRole(new Entities.RoleInfo { AddDate = DateTime.Now, Description = "", Name = "超級管理員", Permissions = adminPermissions }); #endregion return RedirectToAction("Admin", "User"); } catch (Exception ex) { ModelState.AddModelError("", ex.Message); return View(); } } private void createPermission(Controller customController) { var roleService = new RoleService(); var controllerName = ""; var controller = ""; var controllerNo = 0; var actionName = ""; var action = ""; var actionNo = 0; var controllerDesc = new KeyValuePair<string, int>(); var controllerType = customController.GetType(); controller = controllerType.Name.Replace("Controller", "");//remobe controller posfix controllerDesc = getdesc(controllerType); if (!string.IsNullOrEmpty(controllerDesc.Key)) { controllerName = controllerDesc.Key; controllerNo = controllerDesc.Value; foreach (var m in controllerType.GetMethods()) { var mDesc = getPropertyDesc(m); if (string.IsNullOrEmpty(mDesc.Key)) continue; action = m.Name; actionName = mDesc.Key; actionNo = mDesc.Value; roleService.CreatePermissions(actionNo, controllerNo, actionName, controllerName, controller, action); } } } private KeyValuePair<string, int> getdesc(Type type) { var descriptionAttribute = (DescriptionAttribute)(type.GetCustomAttributes(false).FirstOrDefault(x => x is DescriptionAttribute)); if (descriptionAttribute == null) return new KeyValuePair<string, int>(); return new KeyValuePair<string, int>(descriptionAttribute.Name, descriptionAttribute.No); } private KeyValuePair<string, int> getPropertyDesc(System.Reflection.MethodInfo type) { var descriptionAttribute = (DescriptionAttribute)(type.GetCustomAttributes(false).FirstOrDefault(x => x is DescriptionAttribute)); if (descriptionAttribute == null) return new KeyValuePair<string, int>(); return new KeyValuePair<string, int>(descriptionAttribute.Name, descriptionAttribute.No); } } }
以下是DescriptionAttribute的代碼:

using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace Deepleo.Role.Attributes { public class DescriptionAttribute : Attribute { public string Name { set; get; } public int No { set; get; } } }
然后在UserController打上DescriptionAttribute標簽就可以了,如下所示:

using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using Deepleo.Role.Attributes; namespace Deepleo.Role.Controllers { [Description(No = 1, Name = "用戶")] public class UserController : Controller { [RoleAuthorize] [Description(No = 1, Name = "用戶首頁")] public ActionResult Index() { return View(); } [RoleAuthorize] [Description(No = 1, Name = "用戶管理")] public ActionResult Admin() { return View(); } [RoleAuthorize] [Description(No = 1, Name = "用戶詳情")] public ActionResult Detail() { return View(); } } }
這樣在網站安裝的時候直接執行Install就可以完全自動化創建權限。
這樣就可以精確到每個Action的用戶自定義權限控制了。
看看我的勞動成果:
寫在最后:對於同名的Action的HttpGET和HttpPOST分成兩個權限還沒有實現。比如說:New[HttpGet],和New[HttpPOST]。主要是覺得這樣沒有太大的意義,當然如果你的業務需求必須這樣,我覺得應該很容易就能擴展。
完整代碼下載:http://files.cnblogs.com/deepleo/RoleSolution.rar
PS:代碼只有關鍵代碼,沒有實現RoleService方法,請自行根據自己的實際情況實現。