許可權管理-資料許可權
許可權控制可以理解,分為這幾種 :
【功能許可權】:能做什麼的問題,如增加產品。
【資料許可權】:能看到哪些資料的問題,如檢視本人的所有訂單。
【欄位許可權】:能看到哪些資訊的問題,如供應商賬戶,看不到角色、 部門等資訊。
上面提到的那種設計就是【功能許可權】,這種設計有一定的侷限性,對於主體,只能明確地指定。對於不明確的,在這裡可能就沒辦法處理。比如下面這幾種情況:
資料僅當前部門及上級可見
資料僅當前使用者(本人)可見
類似這樣的就需要用到上面提的資料許可權。
上一篇文章我用一個表五個欄位完成了【功能許可權】的設計思路
本文我將介紹如何利用一個表兩個欄位完成這個【資料許可權】的設計思路
初步分析
【資料許可權】是在【功能許可權】的基礎上面進一步的擴充套件,比如可以檢視訂單屬於【功能許可權】的範圍,但是可以檢視哪些訂單就是【資料許可權】的工作了。
在設計中,我們規定好如果沒有設定了資料許可權規則,那麼視為允許檢視全部的資料。
幾個概念
【資源】:資料許可權的控制物件,業務系統中的各種資源。比如訂單單據、銷售單等。屬於上面提到的【領域】中的一種
【主體】:使用者、部門、角色等。
【條件規則】:用於檢索資料的條件定義
【資料規則】:用於【資料許可權】的條件規則
應用場景
1,訂單,可以由本人檢視
2,銷售單,可以由本人或上級領導檢視
3,銷售單,銷售人員可以檢視自己的,銷售經理只檢視 銷售金額大於100,000的。
我們能想到直接的方法,在訪問資料的入口加入SQL Where條件來實現,組織sql語句:
1,where UserID = {CurrentUserID} 2,where UserID = {CurrentUserID} or {CurrentUserID} in (領導) 3,where UserID = {CurrentUserID} or ({CurrentUserID} in (銷售經理) and 銷售金額 > 100000)
這些一個一個的"條件",本文簡單理解為一個【資料規則】,通常會與原來我們前臺的業務過濾條件合併再檢索出資料。
這是一種最直接的實現方式,在【資源】上面加一個【資料規則】(比如上面的三點)。這樣設計就是
【資源】 - 【資料規則】
我又覺得不同的人應該對應不同的規則,那麼也可以理解為,一個使用者對應不同的角色,每一個角色有不一樣的【資料規則】,那麼設計就變成
【資源】 - 【主體】 - 【資料規則】
根據提供者的不同,準備不同的許可權應對策略。
這裡可以簡單地介紹一下,這個方案至少需要2張表,一個是 【資源,主體,規則關係表】、一個是【資料規則表】
關係表不能直接儲存角色,因為你不確定什麼時候業務需要按照【部門】或者【分公司】來定義資料規則
於是可以用 Master、MasterKey 類似這樣的兩個欄位來確定一個【主體】
用XML方式的話是這樣配置的(放在資料庫也類似):
<?xml version="1.0" encoding="utf-8"?> <settings> <rule view="訂單" role="銷售人員"> 銷售員 = {CurrentUserID} </rule> <rule view="訂單" role="總銷售經理"> 銷售金額 > 100000 </rule> <rule view="訂單" role="區域銷售經理"> 銷售金額 > 100000 and 區域 = {當前使用者所屬區域} </rule> </settings>
對於這種方式有興趣的朋友也可以試一下,兩種方式的【資料規則】是一樣的,但是本文沒有采用第二種設計方式,因為它多了一層處理邏輯,我以為應該設計越簡單越好,就採用第一種方式:
【資源】 - 【資料規則】
當然,上面是用SQL的方式來確定條件規則的,我們當然不會這麼做。SQL雖然靈活,但是我們很難去維護,也不知道SQL在我們的介面UI上面如何體現。難不成直接用一個文字框來顯示。這樣對應一個開發人員來說不是問題,可是對應系統管理員,很容易出問題。所以我們需要有另一方式來確定這一規則,並最終可以轉換成我們的SQL語句。
我們的設計關鍵在於如何規範好這些【資料規則】 ,這個規則必須是對前端友好的,而且是對後臺友好的,JSON顯然是很好的方式。
規則說明:
1,資料許可權規則總是:{屬性 條件 允許值}
2,資料許可權規則可以合併。比如 ( {當前使用者 屬於 銷售人員} and {訂單銷售員 等於 當前使用者} ) Or {當前使用者 屬於 銷售經理}
3,最終我們會用JSON格式
在檢索資料時會先判斷有沒有註冊了某某【資源】的【條件規則】,如果有,那麼載入這個【條件規則】併合併到我們前臺的【搜尋條件】(你的業務介面應該有一個搜尋框吧)
如下圖定義了客戶資訊的搜尋框,我們搜尋客戶ID包括AN,我們組織成的規則將會是:
{"rules":[{"field":"CustomerID","op":"like","value":"AN","type":"string"}],"op":"and"}
為了更好地理解【資料規則】,這裡介紹一下【通用查詢機制】
【通用查詢機制】
許可權控制總離不開一些條件的限制(比如檢視當前部門的單據),如果沒有完善的通用查詢規則機制,那麼在做許可權條件過濾的時候你會覺得很彆扭。這裡介紹一個通用查詢方案,然後再介紹如何實現【資料規則】。
早些時候我寫過一篇關於ligerGrid結合.net設計通用處理類的文章《 jQuery
liger ui ligerGrid 打造通用的分頁排序查詢表格(提供下載) 》。裡面提到的過濾資訊是直接的SQL語句。這是不可靠,而且不安全的。
在前端傳輸給後臺的過濾資訊不應該是直接的SQL,而應該是一組過濾規則。在ligerui V1.1.8 已經加入了一個條件過濾器外掛,這個外掛組成的規則資料才是我受推薦的:
比如如下
{"rules":
[
{"field":"OrderDate","op":"less","value":"2012-01-01"},
{"field":"CustomerID","op":"equal","value":"VINET"}
]
,"op":"and"}
規則描述:
查詢顧客VINET所有訂單時間小於2011-01-01的單據
這樣的資料是安全的,而且是通用的(你甚至可以再加一個OR子查詢)。無論是在前端還是後臺,無論你使用什麼樣的元件,都可以很好地利用。
通用後臺的翻譯,就可以生成這樣SQL的引數:
Text: ([OrderDate] < @p1 and [CustomerID] = @p2) Parameters: p1:2012-01-01 p2:VINET
下面來點複雜的:查詢 顧客VINET或者TOMSP,所有訂單時間小於2011-01-01的單據
{
"rules":[{"field":"OrderDate","op":"less","value":"2012-01-01"}],
"groups":[
{"rules":[{"field":"CustomerID","op":"equal","value":"VINET"}, {"field":"CustomerID","op":"equal","value":"TOMSP"}],"op":"or"}
],
"op":"and"
}
翻譯結果:
Text:([OrderDate] < @p1 and ([CustomerID] = @p2 or [CustomerID] = @p3)) Parameters: p1:2012-01-01 p2:VINET p3:TOMSP
這個過濾規則分為三個部分:【分組】、【規則】(欄位、值、操作符)、【操作符】(and or),而自身就是一個分組。
這種簡單的結構就可以滿足全部的情況。
當然,上面提到的這些條件都是在前臺定義(可能是使用者在搜尋框自己輸入的)的,而在後臺,我們可能會定義一下【隱藏條件】,比如說 【員工只能檢視自己的】,要怎麼做呢,其實很簡單,只需要在後臺接收到這個過濾條件(前臺toJSON,後臺解析JSON)以後,再加上一個過濾規則(隱藏條件):
{field:'EmployeeID',op:'equal',value:5}
可以將原來的過濾規則當做一個分組加入進行:
{op:'and',groups:[
{"rules":[{"field":"OrderDate","op":"less","value":"2012-01-01"}],
"groups":[
{"rules":[{"field":"CustomerID","op":"equal","value":"VINET"},{"field":"CustomerID","op":"equal","value":"TOMSP"}],"op":"or"}
],"op":"and"}],rules:[{field:'EmployeeID',op:'equal',value:5}]
}
翻譯如下:
Text: ([EmployeeID] = @p1 and ([OrderDate] < @p2 and ([CustomerID] = @p3 or [CustomerID] = @p4))) Parameters: p1:5 p2:2012-01-01 p3:VINET p4:TOMSP
這樣的【條件規則】才是我們想要的,不僅在前端可以很好地解析,也可以在後臺進行處理。在後臺我們會定義跟這種資料結構對應的類,那麼再定義一個翻譯成SQL的類:
資料許可權規則
說了這些,可以開始介紹如何實現【資料規則】了:
上面提到的【隱藏條件】,就是我介紹的【資料規則】
試想一些,這樣 前臺的過濾規則,再加上我們之間定義好的 【資料許可權】控制 過濾條件。不就達到目的了嗎。
先看看我們在資料庫裡儲存的這些【資料規則】:
看不明白?那來個清楚一點的:
規則描述
訂單:【訂單管理員和演示角色可以檢視所有的】,【訂單檢視員】只能檢視自己的
產品:【基礎資訊錄入員和演示角色可以檢視所有的】,【供應商】只能檢視自己的
{CurrentEmployeeID}表示當前的員工。
實質上,我們還可以根據當前使用者資訊定義需要的引數,比如:
{CurrentUserID} 當前使用者Id ,對應表【CF_User】
{CurrentRoleID} 當前角色Id ,對應表 【CF_Role】
{CurrentDeptID} 當前使用者部門Id,對應表【CF_Department】
{CurrentEmployeeID} 當前使用者員工Id,對應表【Employees】(CF_User.EmployeeID)
{CurrentSupplierID} 當前使用者供應商Id,對應表【Suppliers】(CF_User.SupplierID)
在資料庫中我們直接儲存這些使用者引數,在“翻譯”規則成為SQL時,會替換掉:
比如檢視訂單,我們得到的SQL,可能是這樣的:
Text: SELECT * FROM (SELECT TOP 20 * FROM (SELECT TOP 40 * FROM [Orders] WHERE ( 1=1 and ((@p1 in (@p2,@p3)) or (@p4 = @p5 and [EmployeeID] = @p6))) ORDER BY OrderID ASC) AS tmptableinner ORDER BY OrderID DESC) AS tmptableouter ORDER BY OrderID ASC Parameters: @p1[Int32] = 7 @p2[Int32] = 2 @p3[Int32] = 6 @p4[Int32] = 7 @p5[Int32] = 7 @p6[Int32] = 1
{CurrentRuleID} 替換為 7
{CurrentEmployeeID} 替換為1
下圖是我們設計【資料許可權】的介面,可以選擇所有的欄位,包括幾個使用者資訊:
這些欄位不僅僅只是在文字框中輸入值,那麼可以自定義資料來源:
var fieldEditors = {}; fieldEditors['Orders'] = { 'ShipCity': { type: 'combobox', options: { width: 200, url: "../handler/select.ashx?view=Orders&idfield=ShipCity&textfield=ShipCity&distinct=true" } } };
效果介面:
實際應用
既然是資料許可權控制,如果有一個統一的資料接收入口,我們倒是可以利用這個入口做一些工作。
比如【ligerRM許可權管理系統】統一使用 grid.ashx 這個資料處理程式作為列表資料的接收入口。
有了統一的介面,方便做許可權的控制,使用過 ligerGrid Javascript表格,或者類似外掛的朋友,應該比較清楚伺服器的互動原理。
在grid.ashx中,我們會通過
【檢視/表名 】、 【排序資訊】、【分頁資訊】、【過濾資訊】
這幾個指標來獲取指定的資料。
而在實際的業務中,可能會引入許可權的控制。比如某某【資源】,只能由當前使用者自身才能檢視,或者只能由當前使用者部門及上級部門才能檢視。對於這些控制,我們採用對這些可能做許可權控制的【資源】註冊一組【條件規則】的方式來進行。
我們將找到view定義好的【資料許可權規則】,然後和使用者在前臺搜尋框輸入的【搜尋規則】合併:
上面的程式碼就是資料條件合併的例子,這樣便得到了我們最終需要的 過濾規則。