嘗試asp.net mvc 基於controller action 方式許可權控制方案可行性(轉載)

来源:https://www.cnblogs.com/xhxsk/archive/2018/05/11/9024836.html
-Advertisement-
Play Games

微軟在推出mvc框架不久,短短幾年裡,版本更新之快,真是大快人心,微軟在這種優秀的框架上做了大量的精力投入,是值得贊同的,畢竟程式員駕馭在這種框架上,能夠強力的精化代碼,代碼層次也更加優雅,擴展較為方便,使之程式員把更多的精力投入到業務中來。 很多時候我就在想,是不是該把傳統的用戶許可權管理換個方式了 ...


微軟在推出mvc框架不久,短短幾年裡,版本更新之快,真是大快人心,微軟在這種優秀的框架上做了大量的精力投入,是值得贊同的,畢竟程式員駕馭在這種框架上,能夠強力的精化代碼,代碼層次也更加優雅,擴展較為方便,使之程式員把更多的精力投入到業務中來。

很多時候我就在想,是不是該把傳統的用戶許可權管理換個方式了呢?換成MVC AOP的思想許可權控制,諸如controller action這種方案行的通嗎?答案是肯定的,人的思想永遠是第一位!

看看我們想要達到的效果

1)許可權列表 

 

2)菜單許可權列表

 

3)角色許可權列表

4)用戶許可權列表

5)主從菜單的配置

 

6)圖標的自由定製

 

1.基於controller action控制許可權的好處

其實合起來看,controller與action即控制了一個頁面的行為,如能是否查看,寫入,修改許可權,而我們在開發的過程中,這些方法都已完成,這樣省去了像傳統方式對每個頁面重新控制生成的步驟。當前通過反射程式集收集所有的controller action信息,自動化收集許可權控制是比較可觀的。

2.基於controller action控制許可權的方案

許可權列表通過反射自動從程式集獲取,管理員(預設有最高許可權)把許可權分配給用戶及分配許可權分配給角色,管理員對用戶分配許可權角色許可權,如圖所示

 

用戶許可權列表及角色許可權列表分配成形Controller與Action信息後,通過代碼控制對應的控制器及方法是否有許可權。

 

3.基於controller action控制許可權信息的提取的方案

有些方法不需要提取的,有些方法需要許可權控制,為了讓程式方便提取許可權信息,我們加入特性,如果方法或控制器有此特性,即要控制的,當然為了節約代碼,預設特性是false,這樣,沒有加特性的或者特性是false的,都不用提取!

 

4.基於mvc controller和action 許可權管理流程圖

 

不明白的親們不用太著急,下麵開始詳細的步驟吧!

 

首先我們通過上面的分析,我們用模型來一點一點的剖析

從左至右,相關的模型是,許可權信息列表,角色列表,用戶信息列表,部門列表,菜單列表

1)一個用戶可以有多個許可權,一個許可權可以分配多個用戶,所以是多對多的關係

2)一個角色可以有多個許可權,一個許可權可以分配多個角色,所以是多對多的關係

3)一個用戶可以有多個角色,一個角色可以分配多個用戶,所以是多對多的關係

4)一個用戶可以有多個部門,一個部門可以分配多個角戶,所以是多對多的關係

5)菜單列表,主要針對後臺每個用戶或角色不同的展示方式,以及可以自定義圖片等

 

好了,到此為止,我們開始正式的工作。

由上列模型,我們採用code first生成資料庫,如何使用 code first 請搜索下百度或將來有專門的章節介紹,這裡不再累贅說明!

 

我們添加,添加以後幾個model類

 

 1)許可權控制類:

複製代碼
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace WL.Models.Permission
{
    [DisplayName("控制許可權")]
    public class ActionPermission
    {
        [Key]
        [DisplayName("控制許可權ID")]
        public int ActionPermissonID { set; get; }

        [DisplayName("控制許可權名稱")]
        public string ActionPermissionName { set; get; }

        [DisplayName("控制器許可權名稱")]
        public string ControllerPermissionName { set; get; }

        [DisplayName("說明")]
        public string Description { set; get; }

        [DisplayName("創建時間")]
        public DateTime CreateDate { set; get; }

        [DisplayName("操作用戶名")]
        public string Operator { set; get; }

        [DisplayName("最後修改時間")]
        public DateTime LateDate { set; get; }

        [DisplayName("圖標")]
        public string Icon { get; set; }

        [DisplayName("狀態")]
        public int State { set; get; }

        [Description("用戶實體集合")]
        public virtual ICollection<User> UserCollection { get; set; }

        [Description("角色實體")]
        public virtual ICollection<Role> RoleCollection { get; set; }

    }
}
複製代碼

 

  2)部門類:

複製代碼
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace WL.Models.Permission
{
    [DisplayName("部門")]
    [DisplayColumn("DepartMentID")]
    public class DepartMent
    {
        [Key]
        [DisplayName("部門ID")]
        public int DepartMentID { set; get; }

        [DisplayName("部門名稱")]
        public string DepartName { set; get; }

        [DisplayName("說明")]
        public string Description { set; get; }

        [DisplayName("創建時間")]
        public DateTime CreateDate { set; get; }

        [DisplayName("操作用戶名")]
        public string Operator { set; get; }

        [DisplayName("最後修改時間")]
        public DateTime LateDate { set; get; }

        [DisplayName("圖標")]
        public string Icon { get; set; }


        [DisplayName("狀態")]
        public int State { set; get; }

        [Description("用戶實體集合")]
        public virtual ICollection<User> UserCollection { get; set; }

    }
}
複製代碼

 

   3)菜單類:

複製代碼
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace WL.Models.Permission
{
    [DisplayName("菜單")]
    [DisplayColumn("MenuID")]
    public class Menu
    {
        [Key]
        [DisplayName("菜單ID")]
        public int MenuID { set; get; }

        [DisplayName("ParentID")]
        public int ParentsID { set; get; }

        [DisplayName("菜單名稱")]
        public string MenuName { set; get; }

        [DisplayName("菜單圖標")]
        public string MenuIco { set; get; }

        [DisplayName("說明")]
        public string Descriptotion { set; get; }


        [DisplayName("控制許可權名稱")]
        public string ActionPermissionName { set; get; }

        [DisplayName("控制器許可權名稱")]
        public string ControllerPermissionName { set; get; }

        [DisplayName("鏈接地址")]
        public string Url { set; get; }

        [DisplayName("排序")]
        public string Sort { set; get; }

        [DisplayName("創建時間")]
        public DateTime CreateDate { set; get; }

        [DisplayName("操作用戶名")]
        public string Operator { set; get; }

        [DisplayName("最後修改時間")]
        public DateTime LateDate { set; get; }

        [DisplayName("圖標")]
        public string Icon { get; set; }


        [DisplayName("狀態")]
        public int State { set; get; }

    }
}
複製代碼

 

4)角色類

複製代碼
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel;

namespace WL.Models.Permission
{
    [DisplayName("角色成員")]
    [DisplayColumn("RoleID")]
    public class Role
    {
        [Key]
        [DisplayName("用戶ID")]
        public int RoleID { set; get; }

        [DisplayName("角色名")]
        public string RoleName { set; get; }

        [DisplayName("說明")]
        public string Description { set; get; }

        [DisplayName("創建時間")]
        public DateTime CreateDate { set; get; }

        [DisplayName("操作用戶名")]
        public string Operator { set; get; }

        [DisplayName("最後修改時間")]
        public DateTime LateDate { set; get; }

        [DisplayName("圖標")]
        public string Icon { get; set; }

        [DisplayName("狀態")]
        public int State { set; get; }

        [Description("用戶實體集合")]
        public virtual ICollection<User> UserCollection { get; set; }

        [Description("控制許可權實體集合")]
        public virtual ICollection<ActionPermission> ActionPermissionCollection { get; set; }

    }
}
複製代碼

 

5)用戶後臺管理員類

複製代碼
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel;

namespace WL.Models.Permission
{
    [DisplayName("用戶成員")]
    [DisplayColumn("UserId")]
    public class User
    {
        [Key]
        [DisplayName("用戶ID")]
        public int UserID { set; get; }

        [DisplayName("用戶名稱")]
        public string UserName { set; get; }

        [DisplayName("用戶密碼")]
        public string PassWord { set; get; }

        [DisplayName("用戶郵件")]
        public string Mail { set; get; }

        [DisplayName("用戶電話")]
        public string Phone { set; get; }

        [DisplayName("說明")]
        public string Description { set; get; }

        [DisplayName("創建時間")]
        public DateTime CreateDate { set; get; }

        [DisplayName("操作用戶名")]
        public string Operator { set; get; }

        [DisplayName("最後修改時間")]
        public DateTime LateDate { set; get; }

        [DisplayName("圖標")]
        public string Icon { get; set; }

        [DisplayName("狀態")]
        public int State { set; get; }

        [Description("部門實體")]
        public virtual ICollection<DepartMent> DepartMentCollection { get; set; }

        [Description("角色實體")]
        public virtual ICollection<Role> RoleCollection { get; set; }

        [Description("控制許可權實體集合")]
        public virtual ICollection<ActionPermission> ActionPermissionCollection { get; set; }

    }
}
複製代碼

 

在項目中任意添加一個項目 MvsApp->PermissionContext

 

並配置web.config

 

在此項目中設為啟始項目 然後臺下操作

設置預設項目

 

在命令行里輸入

Enable-Migrations-->Add-Migration Rating-->update-database 

在數據中將會生成許可權資料庫

 

我們採用code first好處在於,“EF”自動為我們創建中間關係表,省去了中間我們手工創建的省麻煩。如果不清楚的朋友,請查EF Code first 自動遷移相關文章。

然後,我們佈局各個控制器如下圖

可以看出,任何一個許可權控制器類都所繼續一個基類baseController,這樣寫的好處只有一個,節約代碼,因為‘懶’。

在基baseControlle類中我們將為許可權控制進行描述。

基於AOP的思想,在此基類baseControlle中,重寫 Controller的 OnActionExecuting()方法,來判斷用戶的相關許可權,大致邏緝流程如下

 

通過上面的流程的分析,代碼如下

1)判斷用否是否成功登錄

複製代碼
  #region -----校驗用戶是否登錄進入網站的-----
            base.OnActionExecuting(filterContext);

            HttpCookie UserInfo = System.Web.HttpContext.Current.Request.Cookies.Get("COOKIE_NAME_FOR_USER");

            //檢驗用戶是否已經登錄,如果登錄則不執行,否則則執行下麵的跳轉代碼
            if (UserInfo == null)
            {
                filterContext.Result = RedirectToRoute(new { Controller = "Login", Action = "Index" });
                return;
            }
            #endregion

            var UserInfoStr = UserInfo["COOKIE_NAME_FOR_USER_INFO"].ToString();

            if (string.IsNullOrEmpty(UserInfoStr))
            {
                filterContext.Result = RedirectToRoute(new { Controller = "Login", Action = "Index" });
                return;
            }
複製代碼

 

2)提取登錄用戶相關登錄的信息(註冊這裡是後臺相關演示,安全性沒做處理)

複製代碼
   var userNameArray = UserInfoStr.Split(new string[] { "$|$" }, StringSplitOptions.RemoveEmptyEntries);
            var username = userNameArray[0];
            var pwdword = userNameArray[1];

            CurrentUserInfo = this.userInfoService.LoadEntites(u => username.Equals(u.UserName) && pwdword.ToLower().Equals(u.PassWord.ToLower()) && u.State == 0).FirstOrDefault();

            if (CurrentUserInfo == null)
            {
                filterContext.Result = RedirectToRoute(new { Controller = "Login", Action = "Index" });
                return;
            }
複製代碼

 

3)獲取當前用戶所在的控制器與方法並根據許可權特性(上文提到的自定義判斷許可權依據)判斷當前控制器是否需要許可權控制

複製代碼
   //先將當前的請求,到許可權表裡面去找對應的數據
            System.Web.Routing.RouteData Rotedate = filterContext.RequestContext.RouteData;
            string controller = (RouteData.Values["controller"] ?? "").ToString().ToLower();
            string action = (RouteData.Values["action"] ?? "").ToString().ToLower();

            //預設
            if (HasDefaultAction(filterContext))
                return;
複製代碼 複製代碼
   private bool HasDefaultAction(ActionExecutingContext filterContext)
        {
            if (CurrentUserInfo.UserName == "admin")
                return true;

            Type t = filterContext.ActionDescriptor.ControllerDescriptor.ControllerType;
            string actionname = filterContext.RouteData.Values["action"].ToString();
            //預設沒用應用許可權特性,僅不需要許可權的才進行控制項該特性,如果應用,HasPermission必須false才可以進行放行些許可權
            object[] astri = GetPermissionAttribute<PermissionAttribute>(actionname, t);
            if (astri.Length > 0)
            {
                //更具自定義的特性得到需要調用的類名與方法名
                PermissionAttribute u = astri[0] as PermissionAttribute;
                return !u.RequiredPermission;
            }
            return false;
        }
複製代碼

 

4)至此用戶程式都通過後,開始用戶相關的許可權判斷。有兩條線路判斷,一是角戶許可權判斷,二是用戶許可權判斷。

角戶許可權判斷

複製代碼
            //然後和許可權表進行對比,如果取出來則通過請求,否則不通過
            //取出當前許可權的數據
            //想去用戶許可權表裡面查詢有沒有數據
            //分析線路 User->Role->Action
            //拿到當前的用戶信息
            var userCurrent = userInfoService.LoadEntites(u => u.UserID == CurrentUserInfo.UserID).FirstOrDefault();
            var role = (from r in userCurrent.RoleCollection
                        from c in r.ActionPermissionCollection
                        where c.ActionPermissionName.ToLower() == action && c.ControllerPermissionName.ToLower() == controller && c.State == 0
                        select r).FirstOrDefault();
            if (role != null)
                return;
複製代碼

 

用戶許可權(後臺用戶)判斷

複製代碼
            //分析線路 User->Action
            var user = (from c in userCurrent.ActionPermissionCollection
                        where c.ActionPermissionName.ToLower() == action && c.ControllerPermissionName.ToLower() == controller && c.State == 0
                        select c).FirstOrDefault();

            if (user != null)
                return;
複製代碼

 

其它可能的錯誤

        public void EndRequest(ActionExecutingContext filterContext)
        {
            filterContext.Result = RedirectToRoute(new { Controller = "Home", Action = "Error" });
        }

 

至此,一個基本的用戶控制已經完成

完整的代碼:

複製代碼
            #region -----校驗用戶是否登錄進入網站的-----
            base.OnActionExecuting(filterContext);

            HttpCookie UserInfo = System.Web.HttpContext.Current.Request.Cookies.Get("COOKIE_NAME_FOR_USER");

            //檢驗用戶是否已經登錄,如果登錄則不執行,否則則執行下麵的跳轉代碼
            if (UserInfo == null)
            {
                filterContext.Result = RedirectToRoute(new { Controller = "Login", Action = "Index" });
                return;
            }
            #endregion

            var UserInfoStr = UserInfo["COOKIE_NAME_FOR_USER_INFO"].ToString();

            if (string.IsNullOrEmpty(UserInfoStr))
            {
                filterContext.Result = RedirectToRoute(new { Controller = "Login", Action = "Index" });
                return;
            }

            var userNameArray = UserInfoStr.Split(new string[] { "$|$" }, StringSplitOptions.RemoveEmptyEntries);
            var username = userNameArray[0];
            var pwdword = userNameArray[1];

            CurrentUserInfo = this.userInfoService.LoadEntites(u => username.Equals(u.UserName) && pwdword.ToLower().Equals(u.PassWord.ToLower()) && u.State == 0).FirstOrDefault();

            if (CurrentUserInfo == null)
            {
                filterContext.Result = RedirectToRoute(new { Controller = "Login", Action = "Index" });
                return;
            }

            #region -------檢驗用戶是否有訪問此地址的權利----
            //先將當前的請求,到許可權表裡面去找對應的數據
            System.Web.Routing.RouteData Rotedate = filterContext.RequestContext.RouteData;
            string controller = (RouteData.Values["controller"] ?? "").ToString().ToLower();
            string action = (RouteData.Values["action"] ?? "").ToString().ToLower();

            //預設
            if (HasDefaultAction(filterContext))
                return;

            //然後和許可權表進行對比,如果取出來則通過請求,否則不通過
            //取出當前許可權的數據
            //想去用戶許可權表裡面查詢有沒有數據
            //分析線路 User->Role->Action
            //拿到當前的用戶信息
            var userCurrent = userInfoService.LoadEntites(u => u.UserID == CurrentUserInfo.UserID).FirstOrDefault();
            var role = (from r in userCurrent.RoleCollection
                        from c in r.ActionPermissionCollection
                        where c.ActionPermissionName.ToLower() == action && c.ControllerPermissionName.ToLower() == controller && c.State == 0
                        select r).FirstOrDefault();
            if (role != null)
                return;

            //分析線路 User->Action
            var user = (from c in userCurrent.ActionPermissionCollection
                        where c.ActionPermissionName.ToLower() == action && c.ControllerPermissionName.ToLower() == controller && c.State == 0
                        select c).FirstOrDefault();

            if (user != null)
                return;

            EndRequest(filterContext);
複製代碼

 

到此,可以看到任何一個控制器都將受到這個基控制器的‘影響’,為此我們沒必要去一個一個的控制器類實現,AOP的思想,確實為我們節約了不少代碼。

簡單的總結下思路:

用戶登錄後寫入cookie ,在此基類中,讀取此cookie存儲的相關用戶相關信息,如用戶名等,同時讀取當前訪問的mvc相關的controller action用戶和角色相關的許可權信息,

如果用戶有此controller action權利,那麼我們即可放行

為了節約資料庫的資源,在此基礎加入判斷,當前訪問的controller action 與用戶當前操作的controller action 是否控制特性判斷,如果在些方法上不存在許可權控制的特性或者為false,那麼,說明不需要許可權控制,直接放行。

 

 (轉載自原作者:谷歌's谷歌's博客園
出處:http://www.cnblogs.com/laogu2/ )

 
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Python可能兩年前,很多人都沒聽過這門編程語言,聽過最多的肯定就是C和java!那麼Python在近兩年為什麼會這麼火呢?很多人都會講肯定是人工智慧,AI這一塊所帶動的,可能也確實如此!此圖為開源中國出的!既然Python這麼火了?那麼你還在等什麼?等和java一樣飽和了在來學Python嗎。風 ...
  • 我是搞控制項開發的,經常被人問,所以把一些問題記錄了下來!如果有人再問,直接把地址丟給他看。 一、 經常會有人抱怨Winform界面閃爍,下麵有幾個方法可以儘可能的避免出現閃爍 1.控制項的使用儘量以純色為主,儘量不使用背景圖,或者把大圖改成小圖,或者圖片不縮放繪製,或者直接用不透明的純色背景色(Win ...
  • 以前的方案 以前寫過一個圓點繞圈的進度條,主要是在cs文件中動態添加圓點,通過定時器設置角度後,用正弦餘弦設置(x,y)的位置。 此方案優點:不需要UI圖標 此方案缺點:定時器耗性能 WPF 繞圈進度條(一) 現在的方案 如果有UI圖標,或者自己能夠設計矢量圖的情況下,可以通過Xaml實現繞圈動畫的 ...
  • 3章 變數與常量 1)變數概念 2)變數類型 3)變數操作 4)常量 4章 表達式與運算符 1)表達式 2)運算符 3)運算符優先順序 錄製視頻:百度雲盤:https://pan.baidu.com/s/19pMkoUW6zCLyVTlyk7DOkw 密碼:ajp8 隨手筆記:百度雲盤:https:/ ...
  • 示例一,正常使用: 相關定義: public interface ICar { int Run(); } public class BMW : ICar { private int _miles = 0; public int Run() { return ++_miles; } } public ...
  • 要引用Newtonsoft.Json.dll https://download.csdn.net/download/jsqdragoon/10032906?web=web ...
  • 我在上篇隨筆《在WinForm應用程式中快速實現多語言的處理》裡面介紹了Winform開發中多語言的處理解決方案,整個多語言解決方案是以實際需求為驅動,以減少代碼改動,高效處理為目的,通過基類繼承的方式減少代碼修改,通過引入翻譯API方式減少翻譯處理時間,本隨筆繼續深化這個多語言處理方案的介紹,是指... ...
  • 在這一篇教程中我們學習如何增加查詢功能,我們將在書籍列表頁面中添加搜索功能,通過按“書籍名稱”或“作者”來搜索書籍。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...