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的特性标记能够在过滤器的上下文中获取到。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏晓晨的专栏

.NET Core 实现 Redis 批量查询指定格式的Key

Redis 作为当前最流行的内存型 NoSQL 数据库,被许多公司所使用,作为分布式缓存。我们在实际使用中一般都会为 key 带上指定的前缀或者其他定义的格式。...

2423
来自专栏dotnet & java

WCF 入门 (21)

其实不太了解为什么第21集才讲这个Binding,下面都是一些概念性的东西,不过作为一个入门视频,了解一下也无妨吧。

865
来自专栏恰同学骚年

自己动手模拟开发一个简单的Web服务器

开篇:每当我们将开发好的ASP.NET网站部署到IIS服务器中,在浏览器正常浏览页面时,可曾想过Web服务器是怎么工作的,其原理是什么?“纸上得来终觉浅,绝知此...

2283
来自专栏hbbliyong

WCF 学习总结2 -- 配置WCF

前面一篇文章《WCF 学习总结1 -- 简单实例》一股脑儿展示了几种WCF部署方式,其中配置文件(App.config/Web.config)都是IDE自动生成...

3017
来自专栏coder修行路

初识go的tomb包

在分析github.com/hpcloud/tail 这个包的源码的时候,发现这个包里用于了一个另外一个包,自己也没有用过,但是这个包在tail这个包里又起来非...

1953
来自专栏NetCore

【翻译】在Visual Studio中使用Asp.Net Core MVC创建你的第一个Web API应用(一)

HTTP is not just for serving up web pages. It’s also a powerful platform for bui...

2465
来自专栏菩提树下的杨过

用VS2010调试微软开放的部分源码

msdn上有一篇讲解如何用vs2008调试源码的文章:http://blogs.msdn.com/b/sburke/archive/2008/01/16/con...

2265
来自专栏菩提树下的杨过

[原创]WCF入门级使用教程(转载请注明出处)

开发环境:vs2008英文版(SP1) + IIS + Windows2003 整个解决方案有4个项目 01.WCF ---Class Libary项目,用于...

2107
来自专栏me的随笔

ASP.NET MVC5请求管道和生命周期

请求管道是一些用于处理HTTP请求的模块组合,在ASP.NET中,请求管道有两个核心组件:IHttpModule和IHttpHandler。所有的HTTP请求都...

1203
来自专栏恰同学骚年

.NET Core微服务之基于MassTransit实现数据最终一致性(Part 2)

  在上一篇中,我们了解了MassTransit这个开源组件的基本用法,这一篇我们结合一个小案例来了解在ASP.NET Core中如何借助MassTransit...

1424

扫码关注云+社区

领取腾讯云代金券