前言
以前也寫過兩篇關於權限這個話題的文章《我所理解的權限》和《Asp.net Mvc 身份驗證、異常處理、權限驗證(攔截器)》,最近在新的項目中,權限設計這塊做了重新的考慮和設計。一直有人說權限這個東西不能太死,所以我們本着把權限盡量做到透明化,而設計了這樣的權限管理。
設計思路
1、因為項目使用的是asp.net mvc,在mvc中一個action就對應的是一個URL,一般來說一個action只會做一件事情,所以我們獲取請求的action就可以知道你將要干什么,那么我們把系統中所有的模塊全部存放到數據庫中,並且把功能按鈕也放到數據庫中,可以構造成一個樹形菜單的形式。如圖:
其中,這里分為導航權限和功能權限,導航權限指:系統運行時會自動根據權限分配加載到導航樹上去的;功能權限指:這個頁面上有哪些按鈕。這里我采用一個標識來區分哪些為導航權限,哪些為功能權限。因為登錄系統,加載導航欄時只需要獲取導航權限進行驗證就OK了。
2、保存用戶權限,在角色賦權的頁面,直接加載整個導航表中的數據,采用樹形展示,在角色表中保存模塊表的主鍵ID即可。
3、為角色關聯用戶則可以直接關聯用戶的ID或者工號什么的都可以。
4、拉取權限加載導航,用戶登錄即可去角色表中獲取到該用戶擁有哪些角色。獲取模塊表ID,根據模塊表ID,獲取模塊表數據,剔除功能權限,然后動態加載導航。
5、功能權限的驗證方案,這也是考慮很久的一個地方,以前的思路就是直接傳一個參數值去判斷是否顯示這個按鈕。這次采用了對HTML進行剔除的方式進行功能按鈕的隱藏和顯示。
具體實現
1、導航權限驗證:采用攔截器,在action執行前進行權限驗證。
public override void OnActionExecuting(ActionExecutingContext filterContext) { //當前訪問地址 string Code = string.Format("/{0}/{1}", filterContext.RouteData.Values["controller"].ToString(), filterContext.RouteData.Values["action"].ToString()).ToLower(); string[] ListResource = ResourceBiz.Instance.Get(new string[] { "Url" }).Select(p => p.Url).Where(p => !string.IsNullOrEmpty(p)).Select(p => p.ToLower()).ToArray();//獲取模塊表導航代碼這個字段與當前URL比對 if (!ListResource.Contains(Code))//模塊表中不存在的URL,默認不進行驗證 return; if (!Authentication.GetResourceCode().Contains(Code))//Authentication.GetResourceCode()為當前用戶所擁有的權限 { //驗證不通過 ContentResult Content = new ContentResult(); Content.Content = "<script type='text/javascript'>alert('權限驗證不通過!');history.go(-1);</script>"; filterContext.Result = Content; } }
2、驗證頁面的功能權限:也是采用攔截器,在action執行后,獲取即將渲染的HTML源碼進行分析及剔除操作。
//獲取即將呈現的HTML,剔除功能按鈕 public override void OnResultExecuted(ResultExecutedContext filterContext) {//用戶控件則不進行篩選 if (!filterContext.IsChildAction) filterContext.HttpContext.Response.Filter = new WhitespaceFilter(filterContext.HttpContext.Response, filterContext);//重寫 }
//重寫 public class WhitespaceFilter : System.IO.MemoryStream { private System.IO.Stream Filter = null; private ResultExecutedContext filterContext = null; private string Source = string.Empty; //構造函數,用來接收變量 public WhitespaceFilter(HttpResponseBase HttpResponseBase, ResultExecutedContext filterContexts) { Filter = HttpResponseBase.Filter; filterContext = filterContexts; } //讀取HTML源碼 public override void Write(byte[] buffer, int offset, int count) { Source += System.Text.Encoding.UTF8.GetString(buffer);//HTML源碼 } //分析進行權限處理 public override void Close() { //當前訪問地址 string Code = string.Format("/{0}/{1}", filterContext.RouteData.Values["controller"].ToString(), filterContext.RouteData.Values["action"].ToString()).ToLower(); string[] ListResource = ResourceBiz.Instance.Get(new string[] { "Url" }).Select(p => p.Url).Where(p => !string.IsNullOrEmpty(p)).Select(p => p.ToLower()).ToArray();//獲取模塊表所有記錄 if (ListResource.Contains(Code))//模塊表中不存在的URL,默認不進行驗證 { //解析處理 HtmlDocument Document = new HtmlDocument(); Document.LoadHtml(Source); HtmlNode htmlNode = Document.DocumentNode;
/*這里需要獲取這個模塊下所有的共功能權限,然后跟你所擁有的這個頁面的功能權限比對,如果不擁有這個功能權限,則可以根據規則獲取到這段HTML,然后刪除掉*/ /*_______________________開始分析處理功能按鈕,這里可以自己增加驗證規則___________________________*/ HtmlNodeCollection hnc = htmlNode.SelectNodes("//a");//獲取需要驗證的功能按鈕HTML,由開發人員自己定義,你也可以給個特定的標識來標識這個標簽為功能按鈕,譬如:htmlNode.SelectNodes("//a[@class='add']");獲取神馬的 if (hnc != null) { foreach (HtmlNode node in hnc) { //拿到所有A標簽,然后把href取出來,跟當前用戶所擁有的功能權限比對,如果相等或者包含則刪除 string CodeStr = node.Attributes["href"] != null ? node.Attributes["href"].Value.ToLower() : ""; node.ParentNode.RemoveAll(); } } } Filter.Write(System.Text.Encoding.UTF8.GetBytes(Source), 0, System.Text.Encoding.UTF8.GetByteCount(Source)); base.Close(); } }
這樣設計的話,系統中關於權限的地方就全部透明化了。只是在功能權限驗證時,要把所有情況全部考慮進去,不知道從文字中大家能不能明白我所需要表達的意思,文筆實在太差了。