ASP.net MVC 基於角色的權限控制系統的實現


一、引言

我們都知道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權限。這樣就需要建立AdminAuthorizeAttributeUserAuthorizeAttribute.這樣做就無法實現用戶自定義權限。

二、基於角色的權限控制系統

基於角色的權限控制系統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;
        }
    }
}
View Code

屬性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);
        }
    }
}
View Code

以下是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;
        }
    }
}
View Code

然后在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();
        }
    }
}
View Code

 

這樣在網站安裝的時候直接執行Install就可以完全自動化創建權限。

這樣就可以精確到每個Action的用戶自定義權限控制了。

看看我的勞動成果:

寫在最后:對於同名的Action的HttpGET和HttpPOST分成兩個權限還沒有實現。比如說:New[HttpGet],和New[HttpPOST]。主要是覺得這樣沒有太大的意義,當然如果你的業務需求必須這樣,我覺得應該很容易就能擴展。

完整代碼下載:http://files.cnblogs.com/deepleo/RoleSolution.rar

PS:代碼只有關鍵代碼,沒有實現RoleService方法,請自行根據自己的實際情況實現。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM