上一次在 .NET MVC 用户权限管理示例教程中讲解了ASP.NET MVC 通过AuthorizeAttribute类的OnAuthorization方法讲解了粗粒度控制权限的方法,接下来讲解基于角色的权限控制方法。
基于角色的权限控制系统RBAC(Role Based Access Control)是目前最流行,也是最通用的权限控制系统。所谓基于角色的权限控制,就是将各个操作权限分组,每一个组就是一个角色,举个例子:管理员拥有所有的权限,编辑就只拥有写文章和发布文章的权限,这里的“管理员”和“编辑”就是一个角色——一系列操作权限的集合。我们只需要将某个角色赋予某个用户那么这个用户就拥有这个角色下的权限集合。
现在我们要做的就是通过把Controller下的每一个Action可以看作是一个权限,然后可以通过方法将每一个权限进行自定义的分组,从而创建一个角色,然后将角色与用户对应起来。
这个类可以拿到ControllerName和ActionName,这样可以根据ControllerName和ActionName判断是什么样的操作,如下面的代码
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Security; using System.Web.Mvc; using System.Web.Routing; namespace SampleMVCWebsite { 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 roleApi = new RoleApi(); 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 = roleApi.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); } } } }
其中RoleApi是角色对象管理的API,这里需要自己设置角色管理。上面的代码中通过FilterContext的ActionDescriptor对象的ControllerDescriptor就可以获取到ControllerName和ActionName。获取到当前用户的角色后,通过查看用户的权限中是否包含了当前访问的Controller中的方法,就能实现权限验证。这里主要使用了ActionDescriptor和ControllerDescriptor,关于这两个类的介绍可以参考MSDN官网。
PS:这里用Ticket的Version存储RoleId。你也可以用其他方式。关于Ticket的知识可以参考微软MSDN官方文档,这里不再敖述。
这个类是继承Attribute,为的是给Action方法打上描述标签,如下面的代码
using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace SampleMVCWebsite { /// <summary> /// Description Attribute /// </summary> public class DescriptionAttribute:Attribute { public string Name { set; get; } public int No { set; get; } } }
Name和NO和Permission类中是ControllerName、ActionName和ControllerNo、ActionNO是对应的。
使用步骤二创建的DescriptionAttribute标签给Controller标上,以便存储Permission的时候获取信息。如下面的代码:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; namespace SampleMVCWebsite.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 Manage() { return View(); } [RoleAuthorize] [Description(No = 1, Name = "用户详情")] public ActionResult Detail() { return View(); } } }
步骤一种有一个role.Permissions,这个Permissions就是当前用户所拥有的权限集合,在实际的项目开发中是把它存储在数据库关系表中的,我们可以将Permissions类如下面的代码这样定义
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace SampleMVCWebsite { public class Permission { /// <summary> /// Permission Id /// </summary> public virtual int Id { set; get; } /// <summary> /// Permission Action No /// </summary> public virtual int ActionNo { set; get; } /// <summary> /// Controller No /// </summary> public virtual int ControllerNo { set; get; } /// <summary> /// Controller Name /// </summary> public virtual string ControllerName { set; get; } /// <summary> /// Permission Action Name /// </summary> public virtual string ActionName { set; get; } /// <summary> /// Controller /// </summary> public virtual string Controller { set; get; } /// <summary> /// Action /// </summary> public virtual string Action { set; get; } } }
属性Controller和Action记录的是权限,ControllerName和ActionName用于显示UI,ControllerNo和ActionNo用于显示顺序控制。其余根据上面的代码注释应该很好理解,这里就不一一陈述了。
因此你需要将Action输入数据库中来实现RoleApi。手动输入如果方法少还好,但是多了就比较麻烦,这样我们可以使用.NET的反射机制来进行权限的创建。先看下面的代码:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using SampleMVCWebsite.Controllers; namespace SampleMVCWebsite { public class InstallController { public class InstallController : Controller { public ActionResult Index() { return View(); } [HttpPost] public ActionResult Index() { try { var roleService = new RoleApi(); #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 {Permission = d, }); } int adminRoleId = roleService.AddRole(new RoleInfo { Name = "管理员", Description = "", Permissions = adminPermissions, AddDate = DateTime.Now, }); #endregion return RedirectToAction("Admin", "Index"); } catch (Exception ex) { ModelState.AddModelError("", ex.Message); return View(); } } private void CreatePermission(Controller customController) { var roleApi = new RoleApi(); var controllerName = ""; var controller = ""; var controllerNo = 0; var actionName = ""; var action = ""; var actionNo = 0; var controllerDesc = new KeyValuePair<string, int>(); var controllerType = customController.GetType(); //remove controller posfix controller = controllerType.Name.Replace("Controller", ""); 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; roleApi.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); } } } }
上面的代码首先通过Getdesc来获取Controller的描述,GetPropertyDesc方法获取方法的描述,这里的方法就是一个独立的权限,然后通过roleApi的CreatePermissions方法将权限的数据写入到数据库中,将数据写入数据库的代码就需要根据自己的数据库读写方法去实现了,强烈建议使用Linq对数据库进行读写。然后再通过roleApi获取到所有的权限并且将所有的权限绑定到Admin角色,保存对应关系。这样我们就完成了权限控制列表生成并且初始化了管理员用户。
通过上面的示例就可以完成ASP.NET MVC 基于角色的权限控制系统,上面的代码中的roleApi需要自己写代码实现对数据库的操作,数据模型就是权限、角色、用户的数据表以及权限月角色以及角色与用户的关系表。