zoukankan      html  css  js  c++  java
  • 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黑马
  • 相关阅读:
    阿里消息队列中间件 RocketMQ 源码分析 —— Message 拉取与消费(上)
    数据库中间件 ShardingJDBC 源码分析 —— SQL 解析(三)之查询SQL
    数据库分库分表中间件 ShardingJDBC 源码分析 —— SQL 解析(六)之删除SQL
    数据库分库分表中间件 ShardingJDBC 源码分析 —— SQL 解析(五)之更新SQL
    消息队列中间件 RocketMQ 源码分析 —— Message 存储
    源码圈 300 胖友的书单整理
    数据库分库分表中间件 ShardingJDBC 源码分析 —— SQL 路由(一)分库分表配置
    数据库分库分表中间件 ShardingJDBC 源码分析 —— SQL 解析(四)之插入SQL
    数据库分库分表中间件 ShardingJDBC 源码分析 —— SQL 路由(二)之分库分表路由
    C#中Math类的用法
  • 原文地址:https://www.cnblogs.com/hmit/p/10769659.html
Copyright © 2011-2022 走看看