Apache Shiro中許可權應用指南:深入理解許可權
0.導引
Shiro定義許可權與明確地界定或聲明一個行為或操作一樣。它是應用程序中原始功能的聲明,僅此而已。許可權是安全策略中的最低級別構造,它們僅僅明確定義應用程序可以執行的操作。
他們根本不描述或完全不在意"誰"能夠執行這些行動。
這裡列舉一些許可權示例,如下所示:
打開文件;
查看『/user/list』頁面;
列印文檔;
刪除『jsmith』用戶等。
定義"誰"(用戶)被允許執行"什麼"(許可權)是以某種方式向用戶分配許可權的一種活動。這總是由應用程序的數據模型來完成,並且可能因應用程序而有很大差異。
例如,許可權可以分組到一個角色中,並且該角色可以與一個或多個用戶對象相關聯。或者某些應用程序可以擁有一組用戶,並且可以為一個組分配一個角色,通過傳遞性關聯,這將意味著該組中的所有用戶都被隱式授予角色中的許可權。
對於如何授予用戶許可權,有很多種變化形式——具體是由應用程序確定如何根據應用程序需求對其進行建模來決定的。
1.通配符許可權
上面的許可權示例,"打開文件"、"查看"user/list"網頁"等都是有效的許可權聲明。然而,解釋這些自然語言字元串並確定用戶是否被允許執行該行為,計算上將是非常困難的。
因此,為了獲得易於處理但仍然可讀的許可權聲明的能力,Shiro提供了強大而直觀的許可權語法,我們稱之為通配符許可權(WildcardPermission)。
1.1簡單用法
假設您希望保護對公司印表機的訪問許可權,以便某些人可以在特定印表機列印,而同時其他人可以查詢當前正在排隊的任務。
一個非常簡單的方法是授予用戶一個"queryPrinter"許可權。然後,您可以通過調用來查看用戶是否具有queryPrinter許可權:
subject.isPermitted("queryPrinter")
這(幾乎)相當於如下語句:
subject.isPermitted(newWildcardPermission("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"定義的功能的超集。
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安全框架的前世今生:緣起、目標和信念
※想成為一名Web開發者?或許應該學習Node.js而不是PHP
TAG:3T趣課堂 |