Apache Shiro中權限應用指南:深入理解權限

作者 崔傳新

0.導引

他們根本不描述或完全不在意"誰"能夠執行這些行動。

這裡列舉一些權限示例,如下所示:

  • 打開文件;

  • 查看‘/user/list’頁面;

  • 打印文檔;

  • 刪除‘jsmith’用戶等。

定義"誰"(用戶)被允許執行"什麼"(權限)是以某種方式向用戶分配權限的一種活動。 這總是由應用程序的數據模型來完成,並且可能因應用程序而有很大差異。

例如,權限可以分組到一個角色中,並且該角色可以與一個或多個用戶對象相關聯。或者某些應用程序可以擁有一組用戶,並且可以為一個組分配一個角色,通過傳遞性關聯,這將意味著該組中的所有用戶都被隱式授予角色中的權限。

對於如何授予用戶權限,有很多種變化形式 ——具體是由應用程序確定如何根據應用程序需求對其進行建模來決定的。

1.通配符權限

1.1簡單用法

假設您希望保護對公司打印機的訪問權限,以便某些人可以在特定打印機打印,而同時其他人可以查詢當前正在排隊的任務。

一個非常簡單的方法是授予用戶一個"queryPrinter"權限。 然後,您可以通過調用來查看用戶是否具有queryPrinter權限:

subject.isPermitted("queryPrinter")

這(幾乎)相當於如下語句:

subject.isPermitted( new WildcardPermission("queryPrinter") )

稍後將有更多關於這方面的內容(但後一句更內涵更多)。

簡單的權限字符串可能適用於簡單的應用程序,但它需要您具有諸如"printPrinter","queryPrinter","managePrinter"等權限。您還可以使用通配符授予用戶"*"權限(授予所有‘名稱化’權限),這意味著他們擁有整個應用程序的所有權限。

但是使用這種方法沒有辦法僅僅說用戶具有"所有打印機權限"。出於這個原因,通配符權限支持多級權限(多部件構成)。

1.2多部件權限

通配符權限支持多個級別或部件的概念。 例如,您可以通過授予用戶權限來重構前面的簡單示例,如:

printer:query

本例中的冒號(英文字符)是一個特殊字符,用於分隔權限字符串中的下一部分。

在本例中,第一部分是正在操作的域(打印機),第二部分是正在執行的操作(查詢)。上面的其他例子可改為:

printer:print

printer:manage

對於可以使用的部件數量沒有限制,因此,在應用中採用的方式完全取決於您的想象力。

1.3多部件值

每個部分可以包含多個值。 因此,您不需要授予用戶"printer:print"和"printer:query"權限,只需授予他們一個:

printer:print,query

這使他們能夠打印和查詢打印機。 並且由於它們被授予了這兩項操作,因此您可以檢查用戶是否有能力通過調用來查詢打印機,方式如下:

subject.isPermitted("printer:query")

這應該返回true值。

1.4部件所有值

如果你想授予用戶某個特定部分的所有值,該怎麼辦? 這樣做比手動列出每個值更方便。 再次,基於通配符,我們可以做到這一點。 如果printer域有3種可能的操作(query,print和manage),則這個實現方式如下:

printer:query,print,manage

簡化為:

printer:*

然後,對"printer:XXX"的任何權限檢查都將返回true。 以這種方式使用通配符比明確列出動作要好,因為如果稍後將新動作添加到應用程序中,則不需要更新該部分中使用通配符的權限。

最後,還可以在通配符權限字符串的任何部分使用通配符標記。 例如,如果您想授予用戶所有域(不僅是打印機)的"查看"操作,您可以這樣授予此權限:

*:view

然後,對"foo:view"的任何權限檢查都會返回true。

2.實例級訪問控制

通配符權限的另一個常見用法是為實例級訪問控制列表(instance-level Access Control Lists)建模。 在這種情況下,您使用三部分:第一部分是域,第二部分是操作,第三部分是正在執行的實例。

例如,你可以這樣:

printer:query:lp7200

printer:print:epsoncolor

第一個定義了查看ID為lp7200打印機的行為。 第二個權限定義了ID為epsoncolor的打印機的打印行為。 如果您將這些權限授予用戶,那麼他們可以在特定實例上執行特定行為。然後你可以在代碼裡做一個檢查,如下:

if(SecurityUtils.getSubject().isPermitted("printer:query:lp7200")){

// Return the current jobs on printerlp7200

//(返回lp7200打印機當前作業及必要處理)

}

這是表達權限的一種非常強大的方式。 但是,必須為所有打印機定義多個實例ID並不能很好地進行擴展,特別是在將新打印機添加到系統中時。這一問題,你可以改為使用通配符來更好的實現:

printer:print:*

這確實可以擴展,以便它也覆蓋了所有新的打印機。 您甚至可以允許訪問所有打印機上的所有操作,操作如下:

printer:*:*

或者單個打印機上的所有操作:

printer:*:lp7200

甚至是單個打印機特定行為:

printer:query,print:lp7200

'*'通配符和','子部分隔符可用於權限的任何部分。

2.1缺失部件

最後要注意的是權限分配:缺少的部分(部件)意味著用戶可以訪問與該部分相對應的所有值。換一種說法:

printer:print與 printer:print:*是相等的。

printer與printer:*:*是相等的。

但是,您只能從字符串的末尾去掉部分(部件),所以下面是不相等:

printer:lp7200與printer:*:lp7200是不相等的

3.檢查權限

儘管為了方便和可擴展性,權限分配使用通配符構造相當多("printer:print:*"=到任何打印機的打印),但運行時的權限檢查應始終基於最具體的權限字符串。

例如,如果用戶有一個UI,並且他們想要將文檔在lp7200打印機上打印,則應該檢查用戶是否被允許這樣做,代碼檢查此操作如下:

if(SecurityUtils.getSubject().isPermitted("printer:print:lp7200")) {

//print the document to the lp7200 printer

//在lp7200打印機上打印文檔

}

該檢查非常具體,明確反映了用戶當時正試圖做的事情。

然而,對於運行時檢查來說,下面的內容不太理想,也是不對的:

if(SecurityUtils.getSubject().isPermitted("printer:print") ) {

//print the document 打印文檔

}

為什麼? 因為第二個例子說"您必須能夠在任何打印機上打印"。也請記住,"printer:print"相當於"printer:print:*"!

因此,這是一個不正確的檢查。 如果當前用戶無法打印到任何打印機,但他們確實有能力在lp7200和epsoncolor打印機上打印。那麼上面的第二個例子永遠不會允許他們打印到lp7200打印機,即使他們已被授予該能力!

因此,經驗法則是在執行權限檢查時使用最具體的權限字符串。 當然,如果你真的只想執行代碼塊,如果允許用戶打印到任何打印機(懷疑,但可能),上面的第二個塊可能是應用程序中其他地方的有效檢查。您的應用程序確定哪些檢查是有意義的,但總的來說,越具體越好。

4.隱含而不相等

為什麼運行時權限檢查應儘可能具體,但權限分配可更寬泛一些? 這是因為權限檢查是通過蘊含邏輯進行評估的,而不是相等性檢查

也就是說,如果用戶被分配了user:*權限,這意味著用戶可以執行user:view操作。 字符串"user:*"顯然不等於"user:view",但前者意味著後者。 "user:*"描述了"user:view"定義的功能的超集。

為了支持隱含規則,所有權限都被轉換為實現org.apache.shiro.authz.Permission接口的對象實例。 這是因為隱含邏輯可以在運行時執行,並且隱含邏輯通常比簡單的字符串相等性檢查更復雜。本文檔中描述的所有通配符行為實際上可以通過org.apache.shiro.authz.permission.WildcardPermission類實現來實現。 以下是一些更多的通配符權限字符串,通過隱含顯示訪問權限:

user:*意味(有權)著也能夠刪除用戶user:delete 。類似的,

user:*:12345意味著還可以更新ID為12345的用戶帳戶user:update:12345。

printer意味著 在任何打印機上打印 printer:print

5.性能考慮

權限檢查比簡單的等同比較更復雜,因此運行時隱含邏輯必須針對每個分配的權限執行。當使用上面顯示的權限字符串時,你隱式地使用Shiro的默認WildcardPermission來執行了必要的隱含邏輯。

Shiro對Realm實現的默認行為是,對於每個權限檢查(例如,調用subject.isPermitted),需要分別隱式檢查分配給該用戶的所有權限(在其組、角色或直接分配給它們)中的所有權限。 Shiro通過在第一次成功檢查後立即返回來"短路"這一過程(即檢查到了就返回,不在檢查其它),以提高性能,但這不是銀彈。

當使用正確的CacheManager(Shiro支持Realm實現)時,用戶、角色和權限被緩存在內存中時,這通常是非常快的。只要知道,使用此默認行為,隨著分配給用戶或其角色或組的權限數量增加,執行檢查的時間必然會增加。

如果Realm實現者有更有效的方法來檢查權限並執行這個隱含邏輯,特別是基於應用程序的數據模型,他們會將它作為其領域isPermitted方法的一部分來實現。默認情況下,Realm/WildcardPermission支持覆蓋了大多數用例的80-90%,但它可能不是用於在運行時存儲和/或檢查擁有大量權限的應用程序的最佳解決方案。


Apache Shiro中權限應用指南:深入理解權限


分享到:


相關文章: