Asp.Net Core 2.0 项目实战(11) 基于OnActionExecuting全局过滤器,页面操做权限过滤控制到按钮级

1.权限管理

  权限管理的基本定义:百度百科

  基于《Asp.Net Core 2.0 项目实战(10) 基于cookie登陆受权认证并实现前台会员、后台管理员同时登陆》咱们作过了登陆认证,登陆是权限的最基础的认证,没有登陆就没有接下来的各类操做权限管理,以及数据权限管理(暂不探讨),这里咱们把登陆看成全局权限,进入系统后再根据不一样的角色或者人员,固定基本功能的展现,当不一样的角色要对功能操做时,就须要验证操做权限,如:查看/添加/修改/删除,也就是咱们常说的控制到按钮级。下面让咱们一步一步来操做实现一下,本篇提供一种权限过滤思路,欢迎讨论指正,全局过滤代码基类已经实现,相关控制页面还在紧急编码中,时间少任务重,但愿你们多体谅。

内容略长:请耐心浏览。

2.约定大于配置

  约定优于配置,也称做按约定编程,是一种软件设计范式,旨在减小软件开发人员需作决定的数量,得到简单的好处,而又不失灵活性。与之对应的就是mvc下控制器和视图的关系。

  本质是说,开发人员仅需规定应用中不符约定的部分。例如,若是模型中有个名为Sale的类,那么数据库中对应的表就会默认命名为sales。只有在偏离这一约定时,例如将该表命名为”products_sold”,才需写有关这个名字的配置。

  为了方便项目快速构建,数据库咱们这里先使用dtcms 5.0的数据库相关表navigation。EF Core生成Model备用。

a)  首先约定后台Controller和Action命名约定,以及属性Attribute类定义

  ##菜单约定##

  1.nav_name尽可能使用controller

  2.全部英文小写

  3.最后一级url不能为空

 

  ##方法定义约定##

  1.属性全nav_name,action_type

  2.属性只有nav_name,判断Action和参数是否为空

  3.属性只有action_type,控制器名作nav_name

  4.根据控制器+Action判断

  5.不是标准方法必须加属性nav_name

  6.控制器标准,保存Action方法不标准,须要传标准参数

b)   定义操做枚举Enum

using System; using System.Collections.Generic; using System.Text; namespace NC.Common { public class JHEnums { /// <summary>
        /// 统一管理操做枚举 /// </summary>
        public enum ActionEnum { /// <summary>
            /// 全部 /// </summary>
 All, /// <summary>
            /// 显示 /// </summary>
 Show, /// <summary>
            /// 查看 /// </summary>
 View, /// <summary>
            /// 添加 /// </summary>
 Add, /// <summary>
            /// 修改 /// </summary>
 Edit, /// <summary>
            /// 删除 /// </summary>
 Delete, /// <summary>
            /// 审核 /// </summary>
 Audit, /// <summary>
            /// 回复 /// </summary>
 Reply, /// <summary>
            /// 确认 /// </summary>
 Confirm, /// <summary>
            /// 取消 /// </summary>
 Cancel, /// <summary>
            /// 做废 /// </summary>
 Invalid, /// <summary>
            /// 生成 /// </summary>
 Build, /// <summary>
            /// 安装 /// </summary>
 Instal, /// <summary>
            /// 卸载 /// </summary>
 UnLoad, /// <summary>
            /// 登陆 /// </summary>
 Login, /// <summary>
            /// 备份 /// </summary>
 Back, /// <summary>
            /// 还原 /// </summary>
 Restore, /// <summary>
            /// 替换 /// </summary>
 Replace, /// <summary>
            /// 复制 /// </summary>
 Copy } }
JHEnums

c)  获取操做权限

#region 操做权限菜单
        /// <summary>
        /// 获取操做权限 /// </summary>
        /// <returns>Dictionary</returns>
        public static Dictionary<string, string> ActionType() { Dictionary<string, string> dic = new Dictionary<string, string>(); dic.Add("Show", "显示"); dic.Add("View", "查看"); dic.Add("Add", "添加"); dic.Add("Edit", "修改"); dic.Add("Delete", "删除"); dic.Add("Audit", "审核"); dic.Add("Reply", "回复"); dic.Add("Confirm", "确认"); dic.Add("Cancel", "取消"); dic.Add("Invalid", "做废"); dic.Add("Build", "生成"); dic.Add("Instal", "安装"); dic.Add("Unload", "卸载"); dic.Add("Back", "备份"); dic.Add("Restore", "还原"); dic.Add("Replace", "替换"); return dic; } #endregion
Utils.ActionType()

 

d)  Action属性类定义

 

using Microsoft.AspNetCore.Mvc.Filters; using System; namespace NC.Lib { /// <summary>
    /// nav_name /// </summary>
    public class NavAttr : Attribute, IFilterMetadata { public NavAttr() { } public NavAttr(string navName, string actionType) { this.NavName = navName; this.ActionType = actionType; } public string NavName { set; get; }//菜单名称
        public string ActionType { set; get; }  //操做类型
 } }

3. 全局过滤实现

3.1 首先定义一个基Controller

  定义好基AdminBase控制器后,全部的后台域Controller都继承此类

 

3.2 首先验证用户是否登陆

Session相关参考3.5 Session操做

 

/// <summary>
        /// 判断管理员是否已经登陆 /// </summary>
        public bool IsAdminLogin() { var bSession = HttpContext.Session.Get(AdminAuthorizeAttribute.AdminAuthenticationScheme); if (bSession == null) { return false; } siteAdminInfo = ByteConvertHelper.Bytes2Object<JhManager>(bSession); //若是Session为Null
            if (siteAdminInfo != null) { return true; } else { //检查Cookies
                var cookieAdmin = HttpContext.AuthenticateAsync(AdminAuthorizeAttribute.AdminAuthenticationScheme); cookieAdmin.Wait(); var adminname = cookieAdmin.Result.Principal.Claims.FirstOrDefault(x => x.Type == "AdminName")?.Value; var adminpwd = cookieAdmin.Result.Principal.Claims.FirstOrDefault(x => x.Type == "AdminPwd")?.Value; if (adminname != "" && adminpwd != "") { JhManager model = dblEf.JhManager.Where(m => m.UserName == adminname && m.Password == adminpwd).FirstOrDefault(); if (model != null) { HttpContext.Session.Set(AdminAuthorizeAttribute.AdminAuthenticationScheme, ByteConvertHelper.Object2Bytes(model));//存储session
                        bSession = HttpContext.Session.Get(AdminAuthorizeAttribute.AdminAuthenticationScheme); siteAdminInfo = ByteConvertHelper.Bytes2Object<JhManager>(bSession); return true; } } } return false; }

3.3 OnActionExecuting重载方法实现过滤

  首先验证登陆,而后判断须要过滤的area,判断是否有跳过属性(SkipAdminAuthorizeAttribute,作登陆的时候定义过),最后判断菜单和按钮的权限。

  这里须要注意的是,OnActionExecuting和AdminAuthorizeAttribute. OnAuthorization的执行顺序,有的网友博客看到的是OnActionExcuting先执行,我这里测试的是先验证属性OnAuthorization,再执行OnActionExecuting;可能附加条件不一样,这里不作过多探讨,调试的时候你们可试试。

 

/// <summary>
        /// 建立过滤器:***全局过滤器*** 过滤除登陆登出等操做权限验证 /// </summary>
        /// <param name="context"></param>
        public override void OnActionExecuting(ActionExecutingContext filterContext) { base.OnActionExecuting(filterContext); //1.验证是否登陆 //2.验证菜单权限 //3.验证按钮权限 //在action执行以前 //判断是否加有SkipAdmin标签
            var skipAuthorize = filterContext.ActionDescriptor.FilterDescriptors.Where(a => a.Filter is SkipAdminAuthorizeAttribute).Any(); if (!skipAuthorize) { //是否系统管理文件夹里文件,Areas》ad_min
                var isPermission = false; //获取controller和action
                var route = filterContext.RouteData.Values; string strArea = route["area"].ToString();//获取区域的名字,ad_min区域下的都须要权限验证
                if (strArea != null && strArea.Equals("ad_min")) { isPermission = true; } //须要验证权限
                if (isPermission) { var currController = route["controller"].ToString(); var curraction = route["action"].ToString(); var exceptCtr = UtilConf.Configuration["Site:exceptCtr"].Replace("", ",");//防止中文逗号
                    var exceptAction = UtilConf.Configuration["Site:exceptAction"].Replace("", ",");//防止中文逗号 //判断是否有例外控制器或Action校验是否例外,跳过验证
                    if (!exceptCtr.Contains(currController.ToLower()) && !exceptAction.Contains(curraction.ToLower())) { //验证是否登陆
                        if (!IsAdminLogin()) { string msg = string.Format("未登陆或登陆超时,请从新登陆!"); filterContext.Result = new RedirectResult("~/ad_min/login?msg=" + WebUtility.UrlEncode(msg)); return; } //验证菜单权限 //验证按钮权限 //自定义方法属性
                        try { //获取属性
                            NavAttr actionAttr = filterContext.ActionDescriptor.FilterDescriptors.Where(a => a.Filter is NavAttr).Select(a => a.Filter).FirstOrDefault() as NavAttr; string strNavName = string.Empty; string strActionType = string.Empty; if (actionAttr == null) { actionAttr = filterContext.ActionDescriptor.FilterDescriptors.GetType().GetCustomAttributes<NavAttr>().FirstOrDefault() as NavAttr; } if (actionAttr != null) { strNavName = actionAttr.NavName; strActionType = actionAttr.ActionType; } //获取参数,因为action在mvc中属于关键词,因此使用act看成操做方式参数
                            string paramAction = ""; //paramAction = Request.Query["action"].ToString();
                            if (string.IsNullOrEmpty(paramAction)) { if (route["act"] != null) { paramAction = route["act"].ToString(); } } if (siteAdminInfo.RoleType != 1)//超管拥有全部权限
 { if (!ChkPermission(siteAdminInfo.RoleId, currController, curraction, strNavName, strActionType, paramAction)) { TempData["Permission"] = "您没有管理该页面的权限,请联系管理员!"; filterContext.Result = new RedirectResult("~/ad_min/Home/Index"); return; //返回固定错误json
 } else { TempData["Permission"] = null; } } } catch (System.Exception ex) { throw ex; } } } } }

  页面权限验证,首先获取到页面的Controller和Action以及Action上面是否包含相关属性NavAttr,校验数据库中是否包含对此属性的定义。

 

/// <summary>
        /// 判断页面 /// </summary>
        /// <param name="role_id">角色id</param>
        /// <param name="currController">当前控制器</param>
        /// <param name="currAction">当前</param>
        /// <param name="navName">方法上的属性</param>
        /// <param name="actionType">操做类型</param>
        /// <param name="paramAction">当为操做方法是传递的参数</param>
        /// <returns>没有权限返回false</returns>
        public bool ChkPermission(int? role_id, string currController, string currAction, string navName, string actionType, string paramAction) { //1.未配置页面,在方法上加属性/ad_min/Settings/SysConfigSave //2.控制器+Action /admin/sys_config/index,/admin/sys_config/add,/admin/sys_config/edit //3.先判断已配置页面/admin/settings/sys_config
            bool result = true; var url = HttpContext.Request.Path.Value; if (url.Contains("/ad_min/home/index"))//后台首页不验证
 { return result; } DataTable dt = chkPermission(role_id); var action_type = actionType; //属性不为空
            if (!string.IsNullOrEmpty(navName) && !string.IsNullOrEmpty(actionType))//属性全
 { DataRow[] dr = dt.Select("nav_name='" + navName + "' and action_type='" + action_type + "'"); result = (dr.Count() > 0); } else if (!string.IsNullOrEmpty(navName) && string.IsNullOrEmpty(actionType))//属性只有nav_name
 { action_type = getActionType(currAction, paramAction); DataRow[] dr = dt.Select("nav_name='" + navName + "' and action_type='" + action_type + "'"); result = (dr.Count() > 0); } else if (string.IsNullOrEmpty(currController) && !string.IsNullOrEmpty(actionType))//控制器名:nav_name,属性只有action_type
 { DataRow[] dr = dt.Select("nav_name='" + currController + "' and action_type='" + action_type + "'"); result = (dr.Count() > 0); } else { //约定大于配置 //控制器名:nav_name //Action:action_type
                if (!string.IsNullOrEmpty(currController) && !string.IsNullOrEmpty(currAction)) { //控制器+action
                    if (currAction.ToLower() == "index")//首页为展现
 { currAction = "View"; } DataRow[] dr = dt.Select("nav_name='" + currController + "' and (action_type='" + currAction + "')"); result = (dr.Count() > 0); } //属性全空,控制器+Action验证不经过,参数不空
                if (!result && !string.IsNullOrEmpty(currController) && !string.IsNullOrEmpty(paramAction))//(控制器)+参数判断
 { //参数可为Edit,Add,Del...
                    DataRow[] dr = dt.Select("nav_name='" + currController + "' and action_type='" + paramAction + "'"); result = (dr.Count() > 0); } if (!result)//控制器+Action验证未经过
 { //配置页面处理
                    DataTable dtNav = GetNavCacheList("link_url='" + url + "'");//根据菜单URL,从缓存中检索调用ID
                    if (dtNav.Rows.Count > 0) { DataRow drNav = dtNav.Rows[0]; string nav_name = drNav["name"].ToString();//nav_name
                        action_type = getActionType(currAction, paramAction); DataRow[] dr = dt.Select("nav_name='" + nav_name + "' and action_type='" + action_type + "'"); result = (dr.Count() > 0); } } } return result; } /// <summary>
        /// 判断是否有权限 /// </summary>
        private DataTable chkPermission(int? role_id) { DataTable dt = CacheHelper.Get("permisson" + role_id) as DataTable; if (dt == null) { string strSql = "SELECT mrv.nav_name,mrv.action_type FROM manager_role mr LEFT JOIN manager_role_value mrv ON mr.id=mrv.role_id WHERE mr.id=@role_id"; DbParameters p = new DbParameters(); p.Add("@role_id", role_id); dt = Dbl.JHCMS.CreateSqlDataTable(strSql, p); CacheHelper.Set("permisson" + role_id, dt); } return dt; } /// <summary>
        /// 1.验证action是否标准约定 /// 2.根据action=''参数获取操做类型 /// </summary>
        private string getActionType(string currAction, string paramAction) { if (currAction.ToLower().Contains("index") || currAction.ToLower().Contains("list"))//首先判断是否首页/列表等展现
 { return "View"; } if (currAction.ToLower().Contains("save"))//若是包含保存save关键字,默认返回add
 { return string.IsNullOrEmpty(paramAction) ? "Add" : paramAction; } else if (currAction.ToLower().Contains("edit") || currAction.ToLower().Contains("update")) { return string.IsNullOrEmpty(paramAction) ? "Edit" : paramAction; } else if (currAction.ToLower().Contains("del")) { return string.IsNullOrEmpty(paramAction) ? "Delete" : paramAction; } //判断Action
            if (!string.IsNullOrEmpty(currAction)) { if (Utils.ActionType().ContainsKey(currAction))//首字母要大写,约定
                    return currAction; } return string.IsNullOrEmpty(paramAction) ? "View" : paramAction; }

3.4 Controller中的约定

  1.NavAttr属性所有定义

 

/// <summary>
        /// 更新字典排序 /// </summary>
        [NavAttr(NavName = "sys_navigation", ActionType = "Edit")] public JsonResult UpdateNav(string id, string nav) {}

  2.NavAttr属性之定义NavName(对应数据库中的name)

 

[NavAttr(NavName = "sys_navigation"] public JsonResult UpdateNav_Edit(string id, string nav) {}

  3.未定义Action属性,必须传递一个参数以肯定操做类型

//(控制器)+参数判断

    public class sys_navigationController : AdminBase { public JsonResult UpdateNav_Edit(string id, string nav) {} }

 

3.5 Session相关操做

  Session使用须要先在startup.cs中进行配置注入,找到方法ConfigureServices注入Session

  Configure中启用

  在控制器中的操做,存储:

JhManager bUser = getUserInfoByNameAndPwd(AdminName, adminpwd, true); HttpContext.Session.Set(AdminAuthorizeAttribute.AdminAuthenticationScheme, ByteConvertHelper.Object2Bytes(bUser));//存储session

  读取:

var bSession = HttpContext.Session.Get(AdminAuthorizeAttribute.AdminAuthenticationScheme); if (bSession == null) { return false; } bUser= ByteConvertHelper.Bytes2Object<JhManager>(bSession);

  ByteConvertHelper是byte转换帮助类

using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Text; namespace NC.Common { /// <summary>
    /// byte转换操做类,主要用于Session存储 /// </summary>
    public class ByteConvertHelper { /// <summary>
        /// 将对象转换为byte数组 /// </summary>
        /// <param name="obj">被转换对象</param>
        /// <returns>转换后byte数组</returns>
        public static byte[] Object2Bytes(object obj) { byte[] serializedResult = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(obj)); return serializedResult; } /// <summary>
        /// 将byte数组转换成对象 /// </summary>
        /// <param name="buff">被转换byte数组</param>
        /// <returns>转换完成后的对象</returns>
        public static object Bytes2Object(byte[] buff) { return JsonConvert.DeserializeObject<object>(Encoding.UTF8.GetString(buff)); } /// <summary>
        /// 将byte数组转换成对象 /// </summary>
        /// <param name="buff">被转换byte数组</param>
        /// <returns>转换完成后的对象</returns>
        public static T Bytes2Object<T>(byte[] buff) { return JsonConvert.DeserializeObject<T>(Encoding.UTF8.GetString(buff)); } } }
View Code

 

4.总结

  实战项目还在一点点开发中,碰到不少坑点,时间也颇有限。工做愈来愈忙,老是抽时间兼顾学习联系,很累。NET技术更新换代很快,公司里还在沿用比较老的技术,可能大多数公司都是这样,程序不得不学新技术,企业不得不用成熟的技术。

  虽然不知道会作到哪一步,碰到的问题积累的点,在这里先记录下来,备查。项目若是成型或可以运行起来看到效果,到时候开源出来。有时候毕竟代码片断或者写博的时候有些地方不容易连贯起来,如今让咱们先一块儿学习吧。

相关文章
相关标签/搜索