授權,也叫通路控制,即在應用中控制誰能通路哪些資源(如通路頁面/編輯資料/頁面操作等)。在授權中需了解的幾個關鍵對象:主體(Subject)、資源(Resource)、權限(Permission)、角色(Role)。(這裡的資源和權限差別是什麼呢?)
主體
主體,即通路應用的使用者,在Shiro中使用Subject代表該使用者。使用者隻有授權後才允許通路相應的資源。
資源
在應用中使用者可以通路的任何東西,比如通路JSP頁面、檢視/編輯某些資料、通路某個業務方法、列印文本等等都是資源。使用者隻要授權後才能通路。
權限
安全政策中的原子授權機關,通過權限我們可以表示在應用中使用者有沒有操作某個資源的權力。即權限表示在應用中使用者能不能通路某個資源,如:
通路使用者清單頁面
檢視/新增/修改/删除使用者資料(即很多時候都是CRUD(增查改删)式權限控制)
列印文檔等等。。。
如上可以看出,權限代表了使用者有沒有操作某個資源的權利,即反映在某個資源上的操作允不允許,不反映誰去執行這個操作。是以後續還需要把權限賦予給使用者,即定義哪個使用者允許在某個資源上做什麼操作(權限)(其實,資源可以預設吧),Shiro不會去做這件事情,而是由實作人員提供。
Shiro支援粗粒度權限(如使用者子產品的所有權限)和細粒度權限(操作某個使用者的權限,即執行個體級别的),後續部分介紹。
角色
角色代表了操作集合,可以了解為權限的集合,一般情況下我們會賦予使用者角色而不是權限,即這樣使用者可以擁有一組權限,賦予權限時比較友善。典型的如:項目經理、技術總監、CTO、開發工程師等都是角色,不同的角色擁有一組不同的權限。
隐式角色:即直接通過角色來驗證使用者有沒有操作權限,如在應用中CTO、技術總監、開發工程師可以使用列印機,假設某天不允許開發工程師使用列印機,此時需要從應用中删除相應代碼;再如在應用中CTO、技術總監可以檢視使用者、檢視權限;突然有一天不允許技術總監檢視使用者、檢視權限了,需要在相關代碼中把技術總監角色從判斷邏輯中删除掉;即粒度是以角色為機關進行通路控制的,粒度較粗;如果進行修改可能造成多處代碼修改。
顯示角色:在程式中通過權限控制誰能通路某個資源,角色聚合一組權限集合;這樣假設哪個角色不能通路某個資源,隻需要從角色代表的權限集合中移除即可;無須修改多處代碼;即粒度是以資源/執行個體為機關的;粒度較細。
請google搜尋“RBAC”和“RBAC新解”分别了解“基于角色的通路控制”“基于資源的通路控制(Resource-Based Access Control)(做過OA的一般都懂在說什麼)”。
Shiro支援三種方式的授權:
程式設計式:通過寫if/else授權代碼塊完成:
Java代碼
<a></a>
Java代碼
沒有權限将抛出相應的異常;
JSP/GSP标簽:在JSP/GSP頁面通過相應的标簽完成:
後續部分将詳細介紹如何使用。
基于角色的通路控制(隐式角色)
1、在ini配置檔案配置使用者擁有的角色(shiro-role.ini)
規則即:“使用者名=密碼,角色1,角色2”,如果需要在應用中判斷使用者是否有相應角色,就需要在相應的Realm中傳回角色資訊,也就是說Shiro不負責維護使用者-角色資訊,需要應用提供,Shiro隻是提供相應的接口友善驗證,後續會介紹如何動态的擷取使用者角色。
2、測試用例(com.github.zhangkaitao.shiro.chapter3.RoleTest)
Shiro提供了hasRole/hasRole(筆誤?)用于判斷使用者是否擁有某個角色/某些權限;但是沒有提供如hashAnyRole用于判斷是否有某些權限中的某一個。
Shiro提供的checkRole/checkRoles和hasRole/hasAllRoles不同的地方是它在判斷為假的情況下會抛出UnauthorizedException異常。
到此基于角色的通路控制(即隐式角色)就完成了,這種方式的缺點就是如果很多地方進行了角色判斷,但是有一天不需要了那麼就需要修改相應代碼把所有相關的地方進行删除;這就是粗粒度造成的問題。
基于資源的通路控制(顯示角色)
1、在ini配置檔案配置使用者擁有的角色及角色-權限關系(shiro-permission.ini)
[users]
zhang=123,role1,role2
wang=123,role1
[roles]
role1=user:create,user:update
role2=user:create,user:delete
規則:“使用者名=密碼,角色1,角色2”“角色=權限1,權限2”,即首先根據使用者名找到角色,然後根據角色再找到權限;即角色是權限集合;Shiro同樣不進行權限的維護,需要我們通過Realm傳回相應的權限資訊。隻需要維護“使用者——角色”之間的關系即可。
2、測試用例(com.github.zhangkaitao.shiro.chapter3.PermissionTest)
Shiro提供了isPermitted和isPermittedAll用于判斷使用者是否擁有某個權限或所有權限,也沒有提供如isPermittedAny用于判斷擁有某一個權限的接口。
但是失敗的情況下會抛出UnauthorizedException異常。
到此基于資源的通路控制(顯示角色)就完成了,也可以叫基于權限的通路控制,這種方式的一般規則是“資源辨別符:操作”,即是資源級别的粒度;這種方式的好處就是如果要修改基本都是一個資源級别的修改,不會對其他子產品代碼産生影響,粒度小。但是實作起來可能稍微複雜點,需要維護“使用者——角色,角色——權限(資源:操作)”之間的關系。
規則:“資源辨別符:操作:對象執行個體ID(沒玩過)” 即對哪個資源的哪個執行個體可以進行什麼操作。其預設支援通配符權限字元串,“:”表示資源/操作/執行個體的分割;“,”表示操作的分割;“*”表示任意資源/操作/執行個體。
1、單個資源單個權限
使用者擁有資源“system:user”的“update”權限。
2、單個資源多個權限
ini配置檔案
然後通過如下代碼判斷
使用者擁有資源“system:user”的“update”和“delete”權限。如上可以簡寫成:
ini配置(表示角色4擁有system:user資源的update和delete權限)
接着可以通過如下代碼判斷
通過“system:user:update,delete”驗證"system:user:update, system:user:delete"是沒問題的,但是反過來是規則不成立。
3、單個資源全部權限
ini配置
然後通過如下代碼判斷
使用者擁有資源“system:user”的“create”、“update”、“delete”和“view”所有權限。如上可以簡寫成:
ini配置檔案(表示角色5擁有system:user的所有權限)
也可以簡寫為(推薦上邊的寫法):
通過“system:user:*”驗證“system:user:create,delete,update:view”可以,但是反過來是不成立的。
4、所有資源全部權限
使用者擁有所有資源的“view”所有權限。假設判斷的權限是“"system:user:view”,那麼需要“role5=*:*:view”這樣寫才行。
5、執行個體級别的權限
5.1、單個執行個體單個權限
ini配置
對資源user的1執行個體擁有view權限。
5.2、單個執行個體多個權限
ini配置
對資源user的1執行個體擁有update、delete權限。
5.3、單個執行個體所有權限
ini配置
對資源user的1執行個體擁有所有權限。
5.4、所有執行個體單個權限
5.5、所有執行個體所有權限
然後通過如下代碼判斷
6、Shiro對權限字元串缺失部分的處理【規則】
如“user:view”等價于“user:view:*”;而“organization”等價于“organization:*”或者“organization:*:*”。可以這麼了解,這種方式實作了字首比對。
另外如“user:*”可以比對如“user:delete”、“user:delete”可以比對如“user:delete:1”、“user:*:1”可以比對如“user:view:1”、“user”可以比對“user:view”或“user:view:1”等。即*可以比對所有,不加*可以進行字首比對;但是如“*:view”不能比對“system:user:view”,需要使用“*:*:view”,即字尾比對必須指定字首(多個冒号就需要多個*來比對)。
7、WildcardPermission
如下兩種方式是等價的:
subject().checkPermission("menu:view:1");
subject().checkPermission(new WildcardPermission("menu:view:1"));
是以沒什麼必要的話使用字元串更友善。
8、性能問題
另外執行個體級别的權限驗證如果資料量太大也不建議使用,可能造成查詢權限及比對變慢。可以考慮比如在sql查詢時加上權限字元串之類的方式在查詢時就完成了權限比對。
流程如下:
1、首先調用Subject.isPermitted*/hasRole*接口,其會委托給SecurityManager,而SecurityManager接着會委托給Authorizer;
2、Authorizer是真正的授權者,如果我們調用如isPermitted(“user:view”),其首先會通過PermissionResolver把字元串轉換成相應的Permission執行個體;
3、在進行授權之前,其會調用相應的Realm擷取Subject相應的角色/權限用于比對傳入的角色/權限;
4、Authorizer會判斷Realm的角色/權限是否和傳入的比對,如果有多個Realm,會委托給ModularRealmAuthorizer進行循環判斷,如果比對如isPermitted*/hasRole*會傳回true,否則傳回false表示授權失敗。
ModularRealmAuthorizer進行多Realm比對流程:
1、首先檢查相應的Realm是否實作了實作了Authorizer;
2、如果實作了Authorizer,那麼接着調用其相應的isPermitted*/hasRole*接口進行比對;
3、如果有一個Realm比對那麼将傳回true,否則傳回false。
如果Realm進行授權的話,應該繼承AuthorizingRealm,其流程是:
1.1、如果調用hasRole*,則直接擷取AuthorizationInfo.getRoles()與傳入的角色比較即可;
1.2、首先如果調用如isPermitted(“user:view”),首先通過PermissionResolver将權限字元串轉換成相應的Permission執行個體,預設使用WildcardPermissionResolver,即轉換為通配符的WildcardPermission;
2、通過AuthorizationInfo.getObjectPermissions()得到Permission執行個體集合;通過AuthorizationInfo. getStringPermissions()得到字元串集合并通過PermissionResolver解析為Permission執行個體;然後擷取使用者的角色,并通過RolePermissionResolver解析角色對應的權限集合(預設沒有實作,可以自己提供);
3、接着調用Permission. implies(Permission p)逐個與傳入的權限比較,如果有比對的則傳回true,否則false。
Authorizer的職責是進行授權(通路控制),是Shiro API中授權核心的入口點,其提供了相應的角色/權限判斷接口,具體請參考其Javadoc。SecurityManager繼承了Authorizer接口,且提供了ModularRealmAuthorizer用于多Realm時的授權比對。PermissionResolver用于解析權限字元串到Permission執行個體,而RolePermissionResolver用于根據角色解析相應的權限集合。
我們可以通過如下ini配置更改Authorizer實作:
對于ModularRealmAuthorizer,相應的AuthorizingSecurityManager會在初始化完成後自動将相應的realm設定進去,我們也可以通過調用其setRealms()方法進行設定。對于實作自己的authorizer可以參考ModularRealmAuthorizer實作即可,在此就不提供示例了。
設定ModularRealmAuthorizer的permissionResolver,其會自動設定到相應的Realm上(其實作了PermissionResolverAware接口),如:
設定ModularRealmAuthorizer的rolePermissionResolver,其會自動設定到相應的Realm上(其實作了RolePermissionResolverAware接口),如:
示例
1、ini配置(shiro-authorizer.ini)
設定securityManager 的realms一定要放到最後,因為在調用SecurityManager.setRealms時會将realms設定給authorizer,并為各個Realm設定permissionResolver和rolePermissionResolver。另外,不能使用IniSecurityManagerFactory建立的IniRealm,因為其初始化順序的問題可能造成後續的初始化Permission造成影響。
2、定義BitAndWildPermissionResolver及BitPermission
BitPermission用于實作位移方式的權限,如規則是:
權限字元串格式:+資源字元串+權限位+執行個體ID;以+開頭中間通過+分割;權限:0 表示所有權限;1 新增(二進制:0001)、2 修改(二進制:0010)、4 删除(二進制:0100)、8 檢視(二進制:1000);如 +user+10 表示對資源user擁有修改/檢視權限。
Permission接口提供了boolean implies(Permission p)方法用于判斷權限比對的;
BitAndWildPermissionResolver實作了PermissionResolver接口,并根據權限字元串是否以“+”開頭來解析權限字元串為BitPermission或WildcardPermission。
3、定義MyRolePermissionResolver
RolePermissionResolver用于根據角色字元串來解析得到權限集合。
此處的實作很簡單,如果使用者擁有role1,那麼就傳回一個“menu:*”的權限。
4、自定義Realm
此時我們繼承AuthorizingRealm而不是實作Realm接口;推薦使用AuthorizingRealm,因為:
AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token):表示擷取身份驗證資訊;
AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals):表示根據使用者身份擷取授權資訊。
這種方式的好處是當隻需要身份驗證時隻需要擷取身份驗證資訊而不需要擷取授權資訊。對于AuthenticationInfo和AuthorizationInfo請參考其Javadoc擷取相關接口資訊。
另外我們可以使用JdbcRealm,需要做的操作如下:
1、執行sql/ shiro-init-data.sql 插入相關的權限資料;
2、使用shiro-jdbc-authorizer.ini配置檔案,需要設定jdbcRealm.permissionsLookupEnabled
為true來開啟權限查詢。
此次還要注意就是不能把我們自定義的如“+user1+10”配置到INI配置檔案,即使有IniRealm完成,因為IniRealm在new完成後就會解析這些權限字元串,預設使用了WildcardPermissionResolver完成,即此處是一個設計權限,如果采用生命周期(如使用初始化方法)的方式進行加載就可以解決我們自定義permissionResolver的問題。
5、測試用例
通過如上步驟可以實作自定義權限驗證了。另外因為不支援hasAnyRole/isPermittedAny這種方式的授權,可以參考我的一篇《簡單shiro擴充實作NOT、AND、OR權限驗證 》進行簡單的擴充完成這個需求,在這篇文章中通過重寫AuthorizingRealm裡的驗證邏輯實作的。
本文轉自二郎三郎部落格園部落格,原文連結:http://www.cnblogs.com/haore147/p/5481870.html,如需轉載請自行聯系原作者