1. 程式人生 > >shiro實戰系列(六)之Authorization(授權)

shiro實戰系列(六)之Authorization(授權)

這樣的 card arch 訪問控制 authorize 列表 數據 nbsp 測試

技術分享圖片

授權,又稱作為訪問控制,是對資源的訪問管理的過程。換句話說,控制誰有權限在應用程序中做什麽。 授權檢查的例子是:該用戶是否被允許訪問這個網頁,編輯此數據,查看此按鈕,或打印到這臺打印機?這些都是 決定哪些是用戶能夠訪問的。

授權的要素:

Apache Shiro 中的權限代表著安全政策中最基礎的元素。它們從根本上作出了對行為的聲明,並明確表示可以在應用程序中做什麽。一個格式良好的權限聲明基本上描述了資源以及當 Subject 與這些資源進行交互時可能出現的行 為。

權限語句的一些例子:

?(1)打開一個文件;

?(2)查看‘/user/list‘網頁;

?(3)打印文檔;

?(4)刪除用戶‘jsmith‘;

大多數資源將支持典型的 CRUD(創建,讀取,更新,刪除)操作,但任何對特定資源有意義的行為都是可以的。 基本的概念是,最小的許可聲明是基於資源和行為的。 在查看權限時,最重要的可能是認識到許可聲明沒有誰可以執行代表行為的表現形式。它們僅僅只是在一個應用程 序中能做什麽的聲明語句。

Permissions represent behavior only

許可聲明僅能夠反映行為(與資源類型相關的行為)。它們不反映是誰能夠執行這樣的行為。

定義(用戶)被允許做什麽(權限),是一個以某種方式分配給用戶權限的運用。這通常是由應用程序的數據模型來完成的,並且不同應用程序間變化很大。 例如,權限能夠被集合到一個角色中,該角色可以與一個或多個用戶對象相關聯。或者某些應用程序可以有一組可 以被分配一個角色的用戶和組,傳遞的關聯意味著該組中的所有用戶都隱式地獲得了該角色的權限。 如何授予用戶權限可以有很多變化——應用程序決定如何基於應用的要求來建模。 我們稍後將討論 Shiro 是如何確定一個 Subject 是否被允許做些什麽。

權限粒度

以上所有權限例子詳細說明了在某一資源類型(入口,文件,客戶等等)的行為(打開,閱讀,刪除等等)。在某些情況下,它們甚至可以指定非常細粒度的實例級的行為——例如,“刪除”(行為)用戶名為"jsmith"的“用戶” (資源類型)。在 Shiro,你有能力來定義這些聲明能夠達到的精確粒度。 我們將在 Shiro 的 Permission 文檔中更加詳細地討論權限粒度和許可聲明的“等級”。

角色權限粒度

以上所有權限例子詳細說明了在某一資源類型(入口,文件,客戶等等)的行為(打開,閱讀,刪除等等)。在某些情況下,它們甚至可以指定非常細粒度的實例級的行為——例如,“刪除”(行為)用戶名為"jsmith"的“用戶” (資源類型)。在 Shiro,你有能力來定義這些聲明能夠達到的精確粒度。 我們將在 Shiro 的 Permission 文檔中更加詳細地討論權限粒度和許可聲明的“等級”。

角色是一個命名的實體,通常代表一組行為或職責。這些行為演化為你在一個軟件應用中能或者不能做的事情。角色通常是分配給用戶帳戶的,因此,通過分配,用戶能夠“做”的事情可以歸屬於各種角色。 有兩種有效類型的角色,並且 Shiro 支持這兩個概念:

(1)隱式角色:大多數人使用的角色作為一個隱式的構造:你的應用程序僅僅基於一個角色名就蘊含了一組行為 (也就是權限)。有了隱式角色,在軟件級別上沒有說“角色 X 被允許執行行為 A,B 和 C”。行為已被一個 單獨的名字所蘊含。

Potentially。

Potentially Brittle Security

雖然有比較簡單和最常用的方法,隱式角色可能強加許多軟件的維護和管理問題。 例如,如果你只是想添加或刪除一個角色,或者重新定義一個角色的行為呢?你不得不返回到你的代碼,並改寫所 有你所有的角色檢查以反映你的安全模型的改變,每次像這樣的改變都是必不可少的!且不說這會招致運營成本(重新測試,經過 QA,關閉應用程序,新的角色下檢查升級軟件,重啟該應用程序等)。 這對非常簡單的應用程序可能沒什麽問題(例如,也許有一個“admin”的角色和“其他人”的角色)。但對於更復雜的或可配置的應用程序,這可能是你的應用程序整個生命周期中最主要問題,並且為你的軟件造成一大筆維護費用。

(2)顯式角色:一個顯式角色本質上是一個實際許可聲明的命名集合。在這種形式下,應用程序(以及 Shiro)確切地知道有沒有一個特定的角色意味著什麽。因為它是已知能不能夠被執行的確切行為,沒有猜測或暗示一 個特定的角色能或不能做什麽。

Shiro 團隊提倡使用權限和顯式角色,而不是陳舊的隱式方法。你將會擁有更多的控制應用程序的安全經驗。

Resource-Based Access Control(基於資源的訪問控制)

請務必閱讀 Les Hazlewood 的文章,新的 RBAC:基於資源的訪問控制, 其中包括深入使用權限和顯式角色(以及它們在源代碼上產生的積極影 響)而不是陳舊的隱式方法的好處

Users(用戶)

用戶實質上是指與應用程序有關的人。然而正如我們已經討論的,Subject 才是 Shiro 的“用戶”概念。 允許用戶(Subjects)在你的應用程序中執行某些操作,是通過與他們的角色相關聯或授予直接的權限。你的應用程序的數據模型定義了 Subject 是如何被允許做某事或不的。 例如,在你的數據模型中,也許你有一個實際的 User 類,而且你直接分配權限給 User 實例。或者,你也許只分配權限給角色,然後分配角色給用戶,通過關聯,用戶延伸“有”的權限分配給自己的角色。或者你用"Group"的概念來代替這些東西。這些都隨便你——使用什麽使得你的程序有意義。 你的數據模型定義授權究竟是如和工作的。Shiro 依靠 Realm 來實現轉換你的數據模型使其細節關聯到一種 Shiro 能 夠理解的格式。

我們一會兒將討論 Realms 是如何做到這一點的。

最終,你的 Realm 的實現是與你的數據源(RDBMS,LDAP 等)進行通信。所以,你的 realm 就是告訴 Shiro 是否存在角色或權限。在你的授權模型結構和定義上你有充分的控制權。

Authorizing Subjects(授權的 Subjects)

在 Shiro 中執行授權可以有 3 種方式:

(1)編寫代碼——你可以在你的 Java 代碼中用像 if 和 else 塊的結構執行授權檢查。 (2)JDK的註解——你可以添加授權註解給你的 Java 方法。 ?

(3)JSP/GSP 標簽庫——你可以控制基於角色和權限的 JSP 或者 GSP 頁面輸出。

(1)Programmatic Authorization(編程授權)

也許最簡單和最常見的方式來執行授權是直接以編程方式與當前 Subject 實例交互。

(2) Role-Based Authorization(基於角色的授權)

如果你想進行基於簡單/傳統的隱式角色名來控制訪問,你可以執行角色檢查

Role checks(角色檢查)

如果你只是簡單的想檢查當前的 Subject 是否擁有一個角色,你可以在 Subject 實例上調用變體的 hasRole*方法。 例如,判斷一個 Subject 是否擁有一個特別的(單一的)角色,你可以通過調用 subject.hasRole 方法,並作出相應的反應:

技術分享圖片

有幾個面向角色的 Subject 方法可以調用,一起取決於你的需要:

技術分享圖片

Role Assertions(角色斷言)

另一種方法通過檢查布爾值來判斷 Subject 是否擁有一個角色,你可以簡單地斷言它們有一個預期的角色在邏輯被執行之前。如果 Subject 沒有預期的角色,AuthorizationException 將會被拋出。如果它們有預期的角色,斷言將悄悄 地執行,並且邏輯將如預期般繼續。

例如:

技術分享圖片

通過使用 hasRole*方法的一個好處就是代碼可以變得清潔,由於你不需要創建你自己的 AuthorizationException 如果 當前的 Subject 不符合預期條件(如果你不想的話)。 有幾個你能

調用的面向角色的 Subject 斷言方法,取決於你的需要:

技術分享圖片

Permission-Based Authorization(基於權限的授權)

如前所述在我們所概述的角色,往往一個更好的方式執行訪問控制是通過基於權限的授權。基於權限的授權,由於 它與你的應用程序的原始功能(以及應用程序核心資源上的行為)緊密的關聯在一起的,基於權限的授權源代碼會 在你的功能改變時改變,而不是在安全政策改變時改變。這意味著代碼很少會被影響對比相似的基於角色的授權的 代碼。

Permission Checks(權限檢查)

如果你想進行檢查,看一個 Subject 是否被允許做某事,你可以調用各種 isPermitted*方法的變種。檢查權限主要有 兩個方式——基於對象的權限實例或代表權限的字符串。

Object-based Permission Checks(基於對象的權限檢查)

執行權限檢查的一個可行方法是實例化 org.apache.shiro.authz.Permission 接口的一個實例,並把它傳遞給接收權限 實例的*isPermitted 方法。 例如,請考慮以下情況:在辦公室有一臺打印機,具有唯一標識符 laserjet4400n。我們的軟件需要檢查當前用戶是 否被允許在該打印機上打印文檔在我們允許他們按下“打印”按鈕之前。上述情況的權限檢查可以明確地像這樣表達:

技術分享圖片

在這個例子中,我也看到了一個非常強大的實例級的訪問控制檢查的例子——限制行為的能力是基於個人的數據實例。 基於對象的權限是很有用的,如果:

?(1)你想編譯時類型安全 ?

(2)你想保證權限被描述和使用是正確的 ?

(3)你想顯式控制許可解析邏輯(被稱作許可蘊含的邏輯,基於權限接口的 implies 方法)是如何執行的。

? (4)你想保證權限反映到應用程序資源是準確的(例如,也許權限類可以在能夠基於項目的域模型的項目編譯時 自動生成)

有幾個你能調用的面向權限的 Subject 方法,取決於你的需要:

技術分享圖片

String-based permission checks(基於字符串的權限檢查)

基於對象的權限可以是很有用的(編譯時類型安全,保證行為,定制蘊含邏輯等),它們有時對應用程序來說會感 到有點“笨手笨腳”的。另一種方法是使用正常的字符串來表示權限。

實例。

例如,基於上面的打印權限的例子上,我們可以重新制訂與之前檢查相同的基於字符串的權限檢查:

技術分享圖片

這個例子還顯示了相同的實例級權限檢查,但權限的重要組成部分——打印機(資源類型),打印(行為),以及 laserjet4400n(實例 ID)——都用一個字符串表示。 這個特別的例子顯示了一個特殊冒號分隔的格式,它由 Shiro 默認的 org.apache.shiro.authz.permission.WildcardPermission 實現來定義,其中大多數人會找到適合自己的格式。 也就是說,上面的代碼塊(大部分)是下面代碼的簡化:

技術分享圖片

WildcardPermission token 規定和構造操作的格式在 Shiro 的 Permission 文檔中被深入的涉及到。 除了上面的字符串默認的 WildcardPermission 格式,你可以創建和使用自己的字符串格式如果你喜歡的話。我們將在 Realm Authorization 這一節討論如何去做。 基於字符串的權限是很有幫助的,由於你不必被迫實現一個接口,而且簡單的字符串易於閱讀。其缺點是,你不具備類型安全,如果你需要更為復雜的行為將超出了字符串所能代表的範圍,你就得實現你自己的基於權限接口的權限對象。在實際中,大部分的Shiro終端用戶為了簡潔選擇基於字符串的方式,但最終你應用程序的需求會決定哪 一個更好。 像基於對象的權限檢查方法一樣,也有字符串的變體來支持基於字符串的權限檢查:

技術分享圖片

Permission Assertions(權限斷言)

作為檢查一個布爾值來判斷 Subject 是被允許做某事的一種替代,你可以在邏輯被執行之前簡單地斷言他們是否擁 有預期的權限。如果該 Subject 是不被允許,AuthorizationException 異常將會被拋出。如果他們如預期的被允許,斷 言將安靜地執行,邏輯也將如預期般繼續。

例如:

技術分享圖片

或者,同樣的檢查,使用字符串權限:

技術分享圖片

通過使用 hasRole*方法的一個好處就是代碼可以變得清潔,由於你不需要創建你自己的 AuthorizationException 如果 當前的 Subject 不符合預期條件(如果你不想的話)。 有幾個你能調用的面向權限的 Subject 斷言方法,取決於你的需要:

技術分享圖片

Annotation-based Authorization(基於註解的授權)

除了 Subject API 的調用,Shiro 提供 Java 5+註解的集合,如果你喜歡以註解為基礎的授權控制

configuration(配置)

在你可以使用 Java 註釋之前,你需要在你的應用程序中啟用 AOP 支持。雖然現在有許多不同的 AOP 框架,但不幸 的是,在應用程序中沒有一個使用 AOP 的標準。

The RequiresAuthentication annotation(RequiresAuthentication 註解) RequiresAuthentication 註解要求當前 Subject 已經在當前的 session 中被驗證通過才能被註解的類/實例/方法訪問或 調用。 例如:

技術分享圖片

The RequiresGuest annotation(RequiresGuest 註解)

RequiresGuest 註解要求當前的 Subject 是一個"guest",也就是說,他們必須是在之前的 session 中沒有被驗證或記住 才能被註解的類/實例/方法訪問或調用。

例如:

技術分享圖片

這通常等同於接下來的基於 Subject 的邏輯

技術分享圖片

The RequiresPermissions annotation(RequiresPermissions 註解)

RequiresPermissions 註解要求當前的 Subject 被允許一個或多個權限,以便執行註解的方法。

技術分享圖片

The RequiresRoles annotation(RequiresRoles 註解) RequiresRoles 註解要求當前的 Subject 擁有所有指定的角色。如果他們沒有,則該方法將不會被執行,而且 AuthorizationException 異常將會被拋出。 例如:

技術分享圖片

The RequiresUser annotation(RequiresUser 註解)

RequiresUser 註解需要當前的 Subject 是一個應用程序用戶才能被註解的類/實例/方法訪問或調用。一個“應用程序 用戶”被定義為一個擁有已知身份,或在當前 session 中由於通過驗證被確認,或者在之前 session 中的‘RememberMe‘ 服務被記住。

技術分享圖片

技術分享圖片

Authorization Sequence(授權順序)

現在我們已經知道了基於當前 Subject 上如何執行授權,讓我們看看當授權調用時,Shiro 內部會發生什麽。 我們采用了 Architecture 那一章的體系結構圖,並只留下與 authorization 有關的組件突出顯示。每個數字代表授權 過程中的一個步驟:

技術分享圖片

Step 1:應用程序或框架代碼調用任何 Subject 的 hasRole*, checkRole*, isPermitted*, 或者 checkPermission*方法的變體,傳遞任何所需的權限或角色代表。

Step 2:Subject 的實例,通常是 DelegatingSubject(或子類)代表應用程序的 SecurityManager 通過調用 securityManager 的幾乎各自相同的 hasRole*, checkRole*, isPermitted*,或 checkPermission*方法的變體(SecurityManager 實現 org.apache.shiro.authz.Authorizer 接口,他定義了所有 Subject 具體的授權方法)。

Step 3:SecurityManager,作為一個基本的“保護傘”組件,接替/代表它內部的 org.apache.shiro.authz.Authorizer 實例通過調用 authorizer 各自的 hasRole*, checkRole*, isPermitted*, 或者 checkPermissions*方法。默認情況下, authorizer 實例是一個 ModularRealmAuthorizer 實例,它支持協調任何授權操作過程中的一個或多個 Realm 實例。

Step 4:每個配置好的 Realm 被檢查是否實現了相同的 Authorizer 接口。如果是,Realm 各自的 hasRole*, checkRole*, isPermitted*,或 checkPermission*方法將被調用。

ModularRealmAuthorizer

如前所述,Shiro SecurityManager 的實現默認是使用一個 ModularRealmAuthorizer 實例。ModularRealmAuthorizer 同樣支持單一的 Realm,以及那些與多個 Realm 的應用。

對於任何授權操作,ModularRealmAuthorizer 將遍歷其內部的 Realm 集合,並按叠代順序與每一個進行交互。每個 Realm 的交互功能如下:

1. 如果 Realm 自己實現了 Authorizer 接口,它的各個 Authorizer 方法(hasRole*, checkRole*, isPermitted*, 或 checkPermission*)將被調用。

(1) 如果 Realm 的方法導致異常,該異常將會以 AuthorizationException 的形式傳遞給調用者。這將短路授權 過程,同時任何剩余的 Realm 將不會被該授權操作所訪問。

(2) 如果該 Realm 的方法是一個返回布爾值的 hasRole*或者 isPermitted*的變體,並且該返回值為 true,真 值將會立即被返回,同時任何剩余的 Realm 都將被短路。這種行為作為提高性能的一種存在,如果該行 為被一個 Realm 允許,這意味著該 Subject 也是被允許的。這有利於安全政策,每一處都是默認被禁止的 情況下,一切都明確允許的,這是安全政策最安全的類型。

2. 如果 Realm 不實現 Authorizer 接口,它會被忽略。

Realm Authorization Order(Realm 的授權順序)

需要重要指出的是,尤其是身份驗證,ModularRealmAuthorizer 將以叠代順序與 Realm 實例進行交互。 ModularRealmAuthorizer 根據 SecurityManager 的配置獲得對 Realm 實例的訪問。當執行授權操作時,它會遍歷該集合,同時對於每一個自己實現 Authorizer 接口的 Realm,調用 Realm 各自的 Authorizer 方法(如 hasRole*, checkRole*, isPermitted*,或 checkPermission*)。

Configuring a global PermissionResolver(配置全局的 PermissionResolver)

當執行基於字符串的權限檢查是,大多數 Shiro 的默認 Realm 實現首先將該字符串轉換成一個實際的 Permission 實例,在執行權限 implication 邏輯之前。 這是因為 Permission 是基於 implication 邏輯評估的,而不是直接的 equality 檢查(見 Permission 文檔有關更多 implication 和 equality 的對比)。Implication 邏輯對比通過字符串比較能夠更好的在代碼中體現。因此,大多數 Realm 需要轉換,或者將提交的權限字符串解析成相應的代表權限的實例。 為了幫助這種轉換,Shiro 支持 PermissionResolver 的概念。大多數 Shiro Realm 的實現使用一個 PermissionResolver 以支持他們的基於字符串權限的 Authorizer 接口方法的實現:當其中一種方法在 Realm 上被調用是,它將使用 PermissionResolver 把該字符串轉換成一個權限實例,並用這種方式來執行檢查。 所有 Shiro Realm 的實現默認是內部的 WildcardPermissionResolver,它采用 Shiro 的 WildcardPermission 字符串格式。 如果你想創建自己的 PermissionResolver 的實現,也許是為了支持自己的權限字符串語法,而且你想要所有配置的 Realm 實例支持該語法,你可以將你的 PermissionResolver 設置為全局的,這樣所有的 Realm 能夠用一個配置。

技術分享圖片

技術分享圖片

Configuring a global RolePermissionResolver(配置全局的 RolePermissionResolver)

與 PermissionResolver 在概念上相似,RolePermissionResolver 有能力代表需要的權限實例,通過一個 Realm 執行權限檢查。 然而,與一個 RolePermissionResolver 的關鍵區別是輸入的字符串是一個角色名,而不是一個權限字符串。 RolePermissionResolver 能夠在 Realm 內部使用,當需要將一個角色名轉換成一組具體的權限實例時。 這是一個特別有用的特征用來支持舊的或不靈活的,可能沒有權限概念的數據源。 例如,許多 LDAP 目錄存儲了角色名(或組名),但是不支持關聯角色名到具體的權限由於他們沒有“權限”的概 念。一個基於Shiro 的應用程序能夠使用存儲在 LDAP 的角色名,還能實現一個 RolePermissionResolver 來轉化 LDAP 名到一組顯式的權限來執行首選的顯式的訪問控制。權限關聯將會被存儲在另一個數據倉庫,可能是一個本地數據 庫。 由於這種轉換角色名到權限的概念非常特定於應用程序,Shiro 默認 Realm 的實現並不使用它們。 然而,如果你想創建你自己的 RolePermissionResolver,並有多個你想配置的 Realm 的實現,你可以將你的 RolePermissionResolver 設置為全局的,這樣所有的 Realm 都能夠用一個配置。

Configuring a global RolePermissionResolver(配置全局的 RolePermissionResolver)

與 PermissionResolver 在概念上相似,RolePermissionResolver 有能力代表需要的權限實例,通過一個 Realm 執行權限檢查。 然而,與一個 RolePermissionResolver 的關鍵區別是輸入的字符串是一個角色名,而不是一個權限字符串。 RolePermissionResolver 能夠在 Realm 內部使用,當需要將一個角色名轉換成一組具體的權限實例時。 這是一個特別有用的特征用來支持舊的或不靈活的,可能沒有權限概念的數據源。 例如,許多 LDAP 目錄存儲了角色名(或組名),但是不支持關聯角色名到具體的權限由於他們沒有“權限”的概 念。一個基於Shiro 的應用程序能夠使用存儲在 LDAP 的角色名,還能實現一個 RolePermissionResolver 來轉化 LDAP 名到一組顯式的權限來執行首選的顯式的訪問控制。權限關聯將會被存儲在另一個數據倉庫,可能是一個本地數據 庫。 由於這種轉換角色名到權限的概念非常特定於應用程序,Shiro 默認 Realm 的實現並不使用它們。 然而,如果你想創建你自己的 RolePermissionResolver,並有多個你想配置的 Realm 的實現,你可以將你的 RolePermissionResolver 設置為全局的,這樣所有的 Realm 都能夠用一個配置。

技術分享圖片

RolePermissionResolverAware 如果你想配置一個全局的 RolePermissionResolver,每個用來接收配置的 RolePermissionResolver 的 Realm 必須實現 RolePermissionResolverAware 接口。這樣保證了配置的全局的 RolePermissionRosolver 實例能夠被每個支持該配置的 Realm 轉發。

如果你不希望使用全局的 RolePermissionResolver 或你不想被 RolePermissionResolverAware 接口所困擾,你可以隨時 顯式地配置一個擁有 RolePermissionResolver 實例的 Realm(假設有一個兼容 JavaBean 的 setRolePermissionResolver 的方法):

技術分享圖片

RolePermissionResolverAware 如果你想配置一個全局的 RolePermissionResolver,每個用來接收配置的 RolePermissionResolver 的 Realm 必須實現 RolePermissionResolverAware 接口。這樣保證了配置的全局的 RolePermissionRosolver 實例能夠被每個支持該配置的 Realm 轉發。

如果你不希望使用全局的 RolePermissionResolver 或你不想被 RolePermissionResolverAware 接口所困擾,你可以隨時 顯式地配置一個擁有 RolePermissionResolver 實例的 Realm(假設有一個兼容 JavaBean 的 setRolePermissionResolver 的方法):

技術分享圖片

如果你不希望使用全局的 RolePermissionResolver 或你不想被 RolePermissionResolverAware 接口所困擾,你可以隨時 顯式地配置一個擁有 RolePermissionResolver 實例的 Realm(假設有一個兼容 JavaBean 的 setRolePermissionResolver 的方法):

技術分享圖片

Custom Authorizer(自定義授權者)

如果你的應用程序使用多個 realm 來執行授權,並且 ModularRealmAuthorizer 默認基於簡單的叠代,短路授權行為 不符合你的要求,你很有可能想創建一個自定義的授權者,並配置相應的 SecurityManager。

技術分享圖片

Understanding Permissions in Apache Shiro

Shiro 將權限定義為一個規定了明確行為或活動的聲明。這是一個在應用程序中的原始功能語句,僅此而已。權限 是在安全策略中最低級別的構造,且它們明確地定義了應用程序只能做“什麽”。 它們從不描述“誰”能夠執行這些動作。

一些權限的例子:

(1) 打開文件

(2) 瀏覽‘/user/list‘頁面

(3) 打印文檔

(4) 刪除‘jsmith‘用戶

規定“誰”(用戶)允許做“什麽”(權限)在某種程度上是分配用權限的一種習慣做法。這始終是通過應用程序 數據模型來完成的,並且在不同應用程序之間差異很大。

例如,權限可以組合到一個角色中,且該角色能夠關聯一個或多個用戶對象。或者某些應用程序能夠擁有一組用戶, 且這個組可以被分配一個角色,通過傳遞的關聯,意味著所有在該組的用戶隱式地獲得了該角色的權限。 如何授予用戶權限可以有很多變化——應用程序基於應用需求來決定如何使其模型化。

Wildcard Permissions 上述的權限例子,“打開文件”、“瀏覽‘/user/list‘頁面”等都是有效的權限語句。然而,將這些解釋為自然語言字符串,並判斷用戶是否被允許執行該行為在計算上是非常困難的。 因此,為了使用易於處理且仍然可讀的權限語句,Shiro 提供了強大而直觀的語法,我們稱之為 WildcardPermission。

Simple Usage

假設你想要保護到貴公司打印機的訪問,使得某些人能夠打印到特定的打印機,而其他人可以查詢當前有哪些工作 在隊列中。

一個極其簡單的方法是授予用戶"queryPrinter"權限。然後你可以檢查用戶是否具有 queryPrinter 權限通過調用

技術分享圖片

簡單的權限字符串可能在簡單的應用程序中工作的很好,但它需要你擁有像"printPrinter","queryPrinter", "managePrinter"等權限。你還可以通過使用通配符授予用戶"*"權限(賦予此權限構造它的名字),這意味著他們在 整個應用程序中擁有了所有的權限。 但使用這種方法不能說用戶擁有“所有打印機權限”。由於這個原因,Wildcard Permissions(通配符權限)支持多 層次的權限管理。

Multiple Parts

通配符權限支持多層次或部件(parts)的概念。例如,你可以通過授予用戶權限來調整之前那個簡單的例子。

技術分享圖片

在這個例子中的冒號是一個特殊字符,它用來分隔權限字符串的下一部件。 在該例中,第一部分是權限被操作的領域(打印機),第二部分是被執行的操作(查詢)。上面其他的例子將被改 為:

技術分享圖片

對於能夠使用的部件是沒有數量限制的,因此它取決於你的想象,依據你可能在你的應用程序中使用的方法。

Multiple Vaules

每個部件能夠保護多個值。因此,除了授予用戶"printer:print"和"printer:query"權限外,你可以簡單地授予他們一個:

技術分享圖片

它能夠賦予用戶 print 和 query 打印機的能力。由於他們被授予了這兩個操作,你可以通過調用下面的語句來判斷用 戶是否有能力查詢打印機:

技術分享圖片

該語句將會返回 true

Multiple Vaules

每個部件能夠保護多個值。因此,除了授予用戶"printer:print"和"printer:query"權限外,你可以簡單地授予他們一個:

技術分享圖片

它能夠賦予用戶 print 和 query 打印機的能力。由於他們被授予了這兩個操作,你可以通過調用下面的語句來判斷用 戶是否有能力查詢打印機:

技術分享圖片

All Values

如果你想在一個特定的部件給某一用戶授予所有的值呢?這將是比手動列出每個值更為方便的事情。同樣,基於通 配符的話,我也可以做到這一點。若打印機域有 3 個可能的操作(query,print 和 manage),可以像下面這樣:

技術分享圖片

然後,任何對"printer:XXX"的權限檢查都將返回 true。以這種方式使用的通配符比明確地列出操作具有更好的尺度, 如果你不久為應用程序增加了一個新的操作,你不需要更新使用通配符那部分的權限。 最後,在一個通配符權限字符串中的任何部分使用通配符 token 也是可以的。例如,如果你想對某個用戶在所有領 域(不僅僅是打印機)授予"view"權限,你可以這樣做:

技術分享圖片

Instance-Level Access Control

另一種常見的通配符權限用法是塑造實例級的訪問控制列表。在這種情況下,你使用三個部件——第一個是域,第 二個是操作,第三個是被付諸實施的實例。

因此像下面這個例子:

技術分享圖片

第一個定義了查詢擁有 ID lp7200 的打印機的行為。第二條權限定義了打印到擁有 ID epsoncolor 的打印機的行為。 如果你授予這些權限給用戶,那麽他們能夠在特定的實例上執行特定的行為。然後你可以在代碼中做一個檢查:

技術分享圖片

這是體現權限的一個極為有效的方法。但同樣,為所有的打印機定義多個實例 ID 能很好的擴展,尤其是當新的打 印機添加到系統的時候。你可以使用通配符來代替:

技術分享圖片

這個做到了擴展,因為它同時涵蓋了任何新的打印機。你甚至可以運行訪問所有打印機上的所有操作:

技術分享圖片

或在一臺打印機上的所有操作:

技術分享圖片

或甚至特定的操作:

技術分享圖片

技術分享圖片

"*"通配符,","子部件分離器可用於權限的任何部分。

Missing Parts 最後要註意的是權限分配:缺少的部件意味著用戶可以訪問所有與之匹配的值,換句話說,

技術分享圖片

技術分享圖片

技術分享圖片

Checking Permissions

雖然權限分配使用通配符較為方便且具有擴展性("printer:print:*" = print to any printer),但在運行時的權限檢查應 該始終基於大多數具體的權限字符串。 例如,如果用戶有一個用戶界面,他們想打印一份文檔到 lp7200 打印機,你應該通過執行這段代碼來檢查用戶是否 被允許這樣做。

技術分享圖片

為什麽?

因為第二個例子表明“對於下面的代碼塊的執行,你必須能夠打印到任何打印機”。但請記住"pinter:print" 是等價於"priner:print:*"的! 因此,這是一個不正確的檢查。如果當前用戶不具備打印到任何打印機的能力,僅僅只有打印到 lp7200 和 epsoncolor 的能力,該怎麽辦呢?那麽上面的第二個例子也絕不允許他們打印到 lp7200 打印機,即使他們已被賦予了相應的能 力! 因此,經驗法則是在執行權限檢查時,盡可能使用權限字符串。當然,上面的第二塊可能是在應用程序中別處的一 個有效檢查,如果你真的想要執行該代碼塊,如果用戶被允許打印到任何打印機(令人懷疑的,但有可能)。你的 應用程序將決定檢查哪些有意義,但一般情況下,越具體越好。

Implication, not Equality

為什麽運行時權限檢查應該盡可能的具體,但權限分配可以較為普通?這是因為權限檢查是通過蘊含的邏輯來判斷 的——而不是通過相等檢查。 也就是說,如果一個用戶被分配了 user:*權限,這意味著該用戶可以執行 user:view 操作。"user:*"字符串明顯不等 於"user:view",但前者包含了後者。"user:*"描述了"user:view"所定義的功能的一個超集。 為了支持蘊含規則,所有的權限都被翻譯到實現 org.apache.shiro.authz.Permission 接口的的對象實例中。這是以便 蘊含邏輯能夠在運行時執行,且蘊含邏輯通常比一個簡單的字符串相等檢查更為復雜。所有在本文檔中描述的通配 符行為實際上是由 org.apache.shiro.authz.permission.WildcardPermission 類實現的。下面是更多的一些通過蘊含邏輯 訪問的通配符權限字符串:

技術分享圖片

技術分享圖片

Performance Considerations

權限檢查比簡單的相等比較要復雜得多,因此運行時的蘊含邏輯必須執行每個分配的權限。當使用像上面展示的權 限字符串時,你正在隱式地使用 Shiro 默認的 WildcardPermission,它能夠執行必要的蘊含邏輯。 Shiro 對 Realm 實現的默認行為是,對於每一個權限驗證(例如,調用 subject.isPermitted),所有分配給該用戶的 權限(在他們的組,角色中,或直接分配給他們)需要為蘊含邏輯進行單獨的檢查。Shiro 通過首次成功檢查立即 返回來“短路”該進程以提高性能,但它不是一顆銀彈。 這通常是極快的,當用戶,角色和權限緩存在內存中且使用了一個合適的 CacheManager 時,在 Shiro 不支持的 Realm 實現中。只要知道使用此默認行為,當權限分配給用戶或他們的角色或組增加時,執行檢查的時間一定會增加。 如果一個 Realm 的實現者有一個更為高效的方式來檢查權限並執行蘊含邏輯,尤其它如果是基於應用程序數據模型 的,他們應該實現它作為 Realm isPermitted* 方法實現的一部分。默認的 Realm/WildcardPermission 存在的支持覆蓋 了大多數用例的 80~90%,但它可能不是在運行時擁有大量權限需要存儲或檢查的應用程序的最佳解決方案。

shiro的授權簡單的一句話概述,該用戶對應的角色有訪問那些資源的權限。

shiro作為javaee項目開發是比較常用的,很多開源項目也在用,什麽gun,jeeste,jeecg等。總而言之,springsecurity並未shiro用的廣。

所以說學shiro,以後java權限方面就再也不怕了。借用一句話概述:“自從有了shiro,什麽樣的權限管理機制我都不怕了”。

shiro實戰系列(六)之Authorization(授權)