• net core体系-web应用程序-4asp.net core2.0 项目实战(1)-13基于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
            }
    }
    复制代码

    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
    复制代码

    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));
            }
        }
    }
    复制代码

    4.总结

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

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

    IT黑马
  • 相关阅读:
    p1373【奶牛的卧室】
    p1248【交错匹配】(DP)
    QBXT模拟赛T3
    NOIP冲刺班的考试总结
    欧拉回路的一些东西
    一道dp题目
    Blocks
    玩具取名
    Y的积木
    游荡的奶牛
  • 原文地址:https://www.cnblogs.com/hmit/p/10769659.html
Copyright © 2020-2023  润新知