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技術更新換代很快,公司裏還在沿用比較老的技術,可能大多數公司都是這樣,程序不得不學新技術,企業不得不用成熟的技術。
雖然不知道會作到哪一步,碰到的問題積累的點,在這裏先記錄下來,備查。項目若是成型或可以運行起來看到效果,到時候開源出來。有時候畢竟代碼片斷或者寫博的時候有些地方不容易連貫起來,如今讓咱們先一塊兒學習吧。