asp.net core權限模塊的快速構建


大部分系統都會有權限模塊,別人家系統的權限怎么生成的我不知道,我只知道這樣做是可以並且挺好的。
文章中只對asp.net core的部分代碼進行說明 呃 記錄~,mvc版本自行前往倉庫查閱
代碼中的一些特性標記后面列出,或前往倉庫查看~

1.根據特性標記生成模塊權限

先上效果圖,感興趣的前往Demo倉庫地址,不感興趣的關閉頁面吧~

圖片

模型定義

Demo中菜單分為三級,首先使用枚舉定義模塊,FirstModuleMenu為一級菜單,SecondModuleMenu為二級菜單,三級菜單在action方法上由PermissionDescription標識並IsMenu=true的方法
若是頁面功能則為IsMenu=false
可使用的特性標記還包含以下幾種,並且權限驗證時依次遞增:

  • 免登錄:AllowAnonymous
  • 管理員默認權限: NonePermissionAttribute
  • 指定權限: PermissionDescriptionAttribute
  • 依賴權限(包含有這些的任一權限都將獲得授權): ParentPermissionAttribute
    //一級菜單
    public class FirstModuleMenu
    {
        public const string 系統管理 = "系統|icon-setting";
        public const string 用戶管理 = "用戶|icon-user";
    }
    //二級菜單
    public enum SecondModuleMenu
    {
        [Description(FirstModuleMenu.系統管理)]
        系統設置,

        [Description(FirstModuleMenu.用戶管理)]
        用戶管理,
    }
    //三級菜單
    [PermissionDescription(SecondModuleMenu.系統設置, "站點設置", true)]
    public ActionResult SiteSetting()
    {
        return Content("站點設置 System/SiteSetting");
    }

生成權限模型集合

定義權限模型 SysModule.cs
調用初始化權限方法

    private static List<SysModule> _AllAdminModule { get; set; } = new List<SysModule>();
    /// <summary>
    /// 初始化權限
    /// </summary>
    public static void InitPermission()
    {
        var result = new List<SysModule>();
        #region 通過反射讀取Action方法寫入對應權限至集合
        //讀取CoreDemo程序集中集成自AdminController的控制器
        var types = Assembly.Load("CoreDemo").GetTypes().Where(e => e.BaseType.Name == nameof(AdminController));
        var area = "";//默認未使用區域
        var now = DateTime.Now;
        var i = 1;
        foreach (var type in types)
        {
            //獲取所有action方法
            var members = type.GetMethods().Where(e => e.ReturnType.Name == nameof(ActionResult) || e.ReturnType.Name == nameof(IActionResult));
            foreach (var member in members)
            {
                //獲取功能列表
                var attrs = member.GetCustomAttributes(typeof(PermissionDescriptionAttribute), true);
                if (attrs.Length == 0)
                    continue;
                //功能對應的二級菜單
                var parentMenuEnum = (attrs[0] as PermissionDescriptionAttribute).ParentMenu;
                var parentMenuName = parentMenuEnum.ToString();
                //功能對應的一級菜單 名稱|icon類名
                var enumArry = parentMenuEnum.GetEnumDescription().Split('|');
                var mainMenuName = enumArry[0];
                var existMainMenu = result.Where(e => e.ModuleName == mainMenuName).FirstOrDefault();
                if (existMainMenu == null)
                {
                    var mainMenu = new SysModule()
                    {
                        Id = i,
                        ParentId = 0,
                        ModuleName = mainMenuName,
                        Icon = enumArry[1] ?? "",
                        Area = area,
                        Controller=string.Empty,
                        Action=string.Empty,
                        IsMenu = true,
                        IsVisible = true,
                        Remark = string.Empty,
                        DisplayOrder = i,
                        CreateTime = now
                    };
                    i++;
                    existMainMenu = mainMenu;
                    existMainMenu.Children = new List<SysModule>();
                    //添加一級菜單
                    result.Add(existMainMenu);
                }
                var existParentMenu = existMainMenu.Children.Where(e => e.ModuleName == parentMenuName).FirstOrDefault();
                if (existParentMenu == null)
                {
                    var parentMenu = new SysModule()
                    {
                        Id = i,
                        ParentId = existMainMenu.Id,
                        ModuleName = parentMenuName,
                        Icon=string.Empty,
                        Area = area,
                        Controller = string.Empty,
                        Action = string.Empty,
                        IsMenu = enumArry.Length != 3 || bool.Parse(enumArry[2]),
                        IsVisible = true,
                        DisplayOrder = i,
                        CreateTime = now,
                        Children = new List<SysModule>()
                    };
                    i++;
                    existParentMenu = parentMenu;
                    existParentMenu.Children = new List<SysModule>();
                    existMainMenu.Children.Add(existParentMenu);
                    //添加二級菜單
                    result.Add(existParentMenu);
                }
                var menu = new SysModule()
                {
                    Id = i,
                    ParentId = existParentMenu.Id,
                    Area = area,
                    Action = member.Name,
                    DisplayOrder = i,
                    CreateTime = now,
                    Controller = member.DeclaringType.Name.Substring(0, member.DeclaringType.Name.Length - 10),
                    IsMenu = (attrs[0] as PermissionDescriptionAttribute).IsMenu,
                    Children = new List<SysModule>(),
                };
                if (menu.IsMenu)
                    menu.IsVisible = true;
                menu.ModuleName = (attrs[0] as PermissionDescriptionAttribute).FuncName;
                i++;
                existParentMenu.Children.Add(menu);
                result.Add(menu);
            }

        }
        #endregion
        //todo 添加到數據庫
        _AllAdminModule = result;
    }

2.使用過濾器攔截請求進行驗證

新建特性標記 AdminAuthorizeAttribute 繼承Attribute類以及實現IAuthorizationFilter接口的OnAuthorization方法
不多說,上圖

圖片

不多說,上代碼↓_↓

權限驗證過濾器:AdminAuthorizeAttribute

    //后台權限驗證
    public class AdminAuthorizeAttribute : Attribute,IAuthorizationFilter
    {

        public void OnAuthorization(AuthorizationFilterContext filterContext)
        {
            //匿名標識 無需驗證
            if (filterContext.Filters.Any(e => (e as AllowAnonymous) != null))
                return;
            var adminInfo = GlobalContext.AdminInfo;//此處應為獲取的登錄用戶
            if (adminInfo == null)
            {
                if(filterContext.HttpContext.Request.Headers["X-Requested-With"] == "XMLHttpRequest")
                {
                    filterContext.Result = new JsonResult("未登錄");
                }
                else
                {
                    filterContext.Result =new ContentResult() { Content = "未登錄" };
                }
                return;
            }
            //對應action方法或者Controller上若存在NonePermissionAttribute標識,即表示為管理員的默認權限,只要登錄就有權限
            var isNone = filterContext.Filters.Any(e => (e as NonePermissionAttribute) != null);
            if (isNone)
                return;

            //獲取請求的區域,控制器,action名稱
            var area = filterContext.RouteData.DataTokens["area"]?.ToString();
            var controller = filterContext.RouteData.Values["controller"]?.ToString();
            var action = filterContext.RouteData.Values["action"]?.ToString();
            var isPermit = false;
            //校驗權限
            isPermit = ServiceFactory.CheckAdminPermit(adminInfo.Id, area, controller, action);
            if (isPermit)
                return;
            //此action方法的父輩權限判斷,只要有此action對應的父輩權限,皆有權限訪問
            var pAttrs = filterContext.Filters.Where(e => (e as ParentPermissionAttribute) != null).ToList();
            if (pAttrs.Count > 0)
            {
                foreach (ParentPermissionAttribute pattr in pAttrs)
                {
                    if (!string.IsNullOrEmpty(pattr.Area))
                        area = pattr.Area;
                    isPermit = ServiceFactory.CheckAdminPermit(adminInfo.Id, area, pattr.Controller, pattr.Action);
                    if (isPermit)
                        return;
                }
            }
            if (!isPermit)
            {
                filterContext.Result = new ContentResult() { Content = "無權限訪問" };
                return;
            }
        }

    }

自定義特性標記,用於權限校驗

此處的自定義的特性標記不能繼承Attribute,因無法在AdminAuthorizeAttribute中的上下文filterContext.Filters中獲取到特性標記(不知道咋取特性標記,所以用這種方式代替,也更為簡單 冏)
!!!!!!!!!修改: 之前腦袋沒有轉過彎來,要使過濾器上下文的Filters中發現自定義過濾器需要繼承 Attribute, IFilterMetadata

    /// <summary>
    /// 管理員的默認權限
    /// </summary>
    public class NonePermissionAttribute : Attribute, IFilterMetadata{}

    /// <summary>
    /// 匿名驗證
    /// </summary>
    public class AllowAnonymous : Attribute, IFilterMetadata{}

    /// <summary>
    /// 長輩權限
    /// </summary>
    [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
    public class ParentPermissionAttribute : Attribute, IFilterMetadata
    {
        /// <summary>
        /// 區域
        /// </summary>
        public string Area { get; set; }
        /// <summary>
        /// 控制器
        /// </summary>
        public string Controller { get; set; }
        /// <summary>
        /// Action名稱
        /// </summary>
        public string Action { get; set; }

        public ParentPermissionAttribute(string area, string controller, string action)
        {
            this.Area = area;
            this.Controller = controller;
            this.Action = action;
        }

        public ParentPermissionAttribute(string controller, string action)
        {
            this.Controller = controller;
            this.Action = action;
        }
    }

若將代碼全部貼出,有點略顯多余,故,只貼出了部分核心代碼.其他一些模型,擴展 請直奔倉庫地址...
或使用git命令克隆MvcPermission分支到MvcPermission文件夾:git clone https://git.coding.net/yimocoding/WeDemo.git -b MvcPermission

補充

  • 2017-09-29
    突然靈光一現,將文中的ResultFilterAttribute特性標記替換為Attribute, IFilterMetadata ,果然可以~
    故得出:實現了IFilterMetadata的特性標記能夠在過濾器的上下文中獲取到。


免責聲明!

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



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