Asp.Net Core 2.0 項目實戰(11) 基於OnActionExecuting全局過濾器,頁面操作權限過濾控制到按鈕級


Asp.Net Core 2.0 項目實戰(1) NCMVC開源下載了

Asp.Net Core 2.0 項目實戰(2)NCMVC一個基於Net Core2.0搭建的角色權限管理開發框架

Asp.Net Core 2.0 項目實戰(3)NCMVC角色權限管理前端UI預覽及下載

Asp.Net Core 2.0 項目實戰(4)ADO.NET操作數據庫封裝、 EF Core操作及實例

Asp.Net Core 2.0 項目實戰(5)Memcached踩坑,基於EnyimMemcachedCore整理MemcachedHelper幫助類。

Asp.Net Core 2.0 項目實戰(6)Redis配置、封裝幫助類RedisHelper及使用實例

Asp.Net Core 2.0 項目實戰(7)MD5加密、AES&DES對稱加解密

Asp.Net Core 2.0 項目實戰(8)Core下緩存操作、序列化操作、JSON操作等Helper集合類

Asp.Net Core 2.0 項目實戰(9) 日志記錄,基於Nlog或Microsoft.Extensions.Logging的實現及調用實例

Asp.Net Core 2.0 項目實戰(10) 基於cookie登錄授權認證並實現前台會員、后台管理員同時登錄

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技術更新換代很快,公司里還在沿用比較老的技術,可能大多數公司都是這樣,程序不得不學新技術,企業不得不用成熟的技術。

  雖然不知道會做到哪一步,碰到的問題積累的點,在這里先記錄下來,備查。項目如果成型或能夠運行起來看到效果,到時候開源出來。有時候畢竟代碼片段或者寫博的時候有些地方不容易連貫起來,現在讓我們先一起學習吧。

 


免責聲明!

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



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