1. 程式人生 > >許可權管理-資料許可權

許可權管理-資料許可權

許可權控制可以理解,分為這幾種 :

【功能許可權】:能做什麼的問題,如增加產品。
【資料許可權】:能看到哪些資料的問題,如檢視本人的所有訂單。
【欄位許可權】:能看到哪些資訊的問題,如供應商賬戶,看不到角色、 部門等資訊。

上面提到的那種設計就是【功能許可權】,這種設計有一定的侷限性,對於主體,只能明確地指定。對於不明確的,在這裡可能就沒辦法處理。比如下面這幾種情況:

資料僅當前部門及上級可見
資料僅當前使用者(本人)可見

類似這樣的就需要用到上面提的資料許可權。

上一篇文章我用一個表五個欄位完成了【功能許可權】的設計思路
本文我將介紹如何利用一個表兩個欄位完成這個【資料許可權】的設計思路

初步分析

【資料許可權】是在【功能許可權】的基礎上面進一步的擴充套件,比如可以檢視訂單屬於【功能許可權】的範圍,但是可以檢視哪些訂單就是【資料許可權】的工作了。

在設計中,我們規定好如果沒有設定了資料許可權規則,那麼視為允許檢視全部的資料。

幾個概念
【資源】:資料許可權的控制物件,業務系統中的各種資源。比如訂單單據、銷售單等。屬於上面提到的【領域】中的一種
【主體】:使用者、部門、角色等。
【條件規則】:用於檢索資料的條件定義
【資料規則】:用於【資料許可權】的條件規則 

 
應用場景
1,訂單,可以由本人檢視 
2,銷售單,可以由本人或上級領導檢視 
3,銷售單,銷售人員可以檢視自己的,銷售經理只檢視 銷售金額大於100,000的。

我們能想到直接的方法,在訪問資料的入口加入SQL Where條件來實現,組織sql語句:

1where UserID = {CurrentUserID}
2where UserID = {CurrentUserID}  or {CurrentUserID} in (領導)
3where 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定義好的【資料許可權規則】,然後和使用者在前臺搜尋框輸入的【搜尋規則】合併:

上面的程式碼就是資料條件合併的例子,這樣便得到了我們最終需要的 過濾規則。