|
目錄貼: 跟我學(xué)Shiro目錄貼
授權(quán),也叫訪問控制,即在應(yīng)用中控制誰能訪問哪些資源(如訪問頁面/編輯數(shù)據(jù)/頁面操作等)。在授權(quán)中需了解的幾個關(guān)鍵對象:主體(Subject)、資源(Resource)、權(quán)限(Permission)、角色(Role)。 主體 主體,即訪問應(yīng)用的用戶,在Shiro中使用Subject代表該用戶。用戶只有授權(quán)后才允許訪問相應(yīng)的資源。 資源 在應(yīng)用中用戶可以訪問的任何東西,比如訪問JSP頁面、查看/編輯某些數(shù)據(jù)、訪問某個業(yè)務(wù)方法、打印文本等等都是資源。用戶只要授權(quán)后才能訪問。 權(quán)限 安全策略中的原子授權(quán)單位,通過權(quán)限我們可以表示在應(yīng)用中用戶有沒有操作某個資源的權(quán)力。即權(quán)限表示在應(yīng)用中用戶能不能訪問某個資源,如: 訪問用戶列表頁面 查看/新增/修改/刪除用戶數(shù)據(jù)(即很多時候都是CRUD(增查改刪)式權(quán)限控制) 打印文檔等等。。。
如上可以看出,權(quán)限代表了用戶有沒有操作某個資源的權(quán)利,即反映在某個資源上的操作允不允許,不反映誰去執(zhí)行這個操作。所以后續(xù)還需要把權(quán)限賦予給用戶,即定義哪個用戶允許在某個資源上做什么操作(權(quán)限),Shiro不會去做這件事情,而是由實現(xiàn)人員提供。
Shiro支持粗粒度權(quán)限(如用戶模塊的所有權(quán)限)和細粒度權(quán)限(操作某個用戶的權(quán)限,即實例級別的),后續(xù)部分介紹。 角色 角色代表了操作集合,可以理解為權(quán)限的集合,一般情況下我們會賦予用戶角色而不是權(quán)限,即這樣用戶可以擁有一組權(quán)限,賦予權(quán)限時比較方便。典型的如:項目經(jīng)理、技術(shù)總監(jiān)、CTO、開發(fā)工程師等都是角色,不同的角色擁有一組不同的權(quán)限。 隱式角色:即直接通過角色來驗證用戶有沒有操作權(quán)限,如在應(yīng)用中CTO、技術(shù)總監(jiān)、開發(fā)工程師可以使用打印機,假設(shè)某天不允許開發(fā)工程師使用打印機,此時需要從應(yīng)用中刪除相應(yīng)代碼;再如在應(yīng)用中CTO、技術(shù)總監(jiān)可以查看用戶、查看權(quán)限;突然有一天不允許技術(shù)總監(jiān)查看用戶、查看權(quán)限了,需要在相關(guān)代碼中把技術(shù)總監(jiān)角色從判斷邏輯中刪除掉;即粒度是以角色為單位進行訪問控制的,粒度較粗;如果進行修改可能造成多處代碼修改。 顯示角色:在程序中通過權(quán)限控制誰能訪問某個資源,角色聚合一組權(quán)限集合;這樣假設(shè)哪個角色不能訪問某個資源,只需要從角色代表的權(quán)限集合中移除即可;無須修改多處代碼;即粒度是以資源/實例為單位的;粒度較細。
請google搜索“RBAC”和“RBAC新解”分別了解“基于角色的訪問控制”“基于資源的訪問控制(Resource-Based Access Control)”。
3.1 授權(quán)方式Shiro支持三種方式的授權(quán):
編程式:通過寫if/else授權(quán)代碼塊完成: Java代碼
注解式:通過在執(zhí)行的Java方法上放置相應(yīng)的注解完成: Java代碼
沒有權(quán)限將拋出相應(yīng)的異常;
JSP/GSP標(biāo)簽:在JSP/GSP頁面通過相應(yīng)的標(biāo)簽完成: Java代碼
后續(xù)部分將詳細介紹如何使用。
3.2 授權(quán)基于角色的訪問控制(隱式角色)
1、在ini配置文件配置用戶擁有的角色(shiro-role.ini) Java代碼
規(guī)則即:“用戶名=密碼,角色1,角色2”,如果需要在應(yīng)用中判斷用戶是否有相應(yīng)角色,就需要在相應(yīng)的Realm中返回角色信息,也就是說Shiro不負責(zé)維護用戶-角色信息,需要應(yīng)用提供,Shiro只是提供相應(yīng)的接口方便驗證,后續(xù)會介紹如何動態(tài)的獲取用戶角色。
2、測試用例(com.github.zhangkaitao.shiro.chapter3.RoleTest) Java代碼
Shiro提供了hasRole/hasRole用于判斷用戶是否擁有某個角色/某些權(quán)限;但是沒有提供如hashAnyRole用于判斷是否有某些權(quán)限中的某一個。
Java代碼
Shiro提供的checkRole/checkRoles和hasRole/hasAllRoles不同的地方是它在判斷為假的情況下會拋出UnauthorizedException異常。
到此基于角色的訪問控制(即隱式角色)就完成了,這種方式的缺點就是如果很多地方進行了角色判斷,但是有一天不需要了那么就需要修改相應(yīng)代碼把所有相關(guān)的地方進行刪除;這就是粗粒度造成的問題。
基于資源的訪問控制(顯示角色) 1、在ini配置文件配置用戶擁有的角色及角色-權(quán)限關(guān)系(shiro-permission.ini) Java代碼
規(guī)則:“用戶名=密碼,角色1,角色2”“角色=權(quán)限1,權(quán)限2”,即首先根據(jù)用戶名找到角色,然后根據(jù)角色再找到權(quán)限;即角色是權(quán)限集合;Shiro同樣不進行權(quán)限的維護,需要我們通過Realm返回相應(yīng)的權(quán)限信息。只需要維護“用戶——角色”之間的關(guān)系即可。
2、測試用例(com.github.zhangkaitao.shiro.chapter3.PermissionTest) Java代碼
Shiro提供了isPermitted和isPermittedAll用于判斷用戶是否擁有某個權(quán)限或所有權(quán)限,也沒有提供如isPermittedAny用于判斷擁有某一個權(quán)限的接口。
Java代碼
但是失敗的情況下會拋出UnauthorizedException異常。
到此基于資源的訪問控制(顯示角色)就完成了,也可以叫基于權(quán)限的訪問控制,這種方式的一般規(guī)則是“資源標(biāo)識符:操作”,即是資源級別的粒度;這種方式的好處就是如果要修改基本都是一個資源級別的修改,不會對其他模塊代碼產(chǎn)生影響,粒度小。但是實現(xiàn)起來可能稍微復(fù)雜點,需要維護“用戶——角色,角色——權(quán)限(資源:操作)”之間的關(guān)系。
3.3 Permission字符串通配符權(quán)限規(guī)則:“資源標(biāo)識符:操作:對象實例ID” 即對哪個資源的哪個實例可以進行什么操作。其默認支持通配符權(quán)限字符串,“:”表示資源/操作/實例的分割;“,”表示操作的分割;“*”表示任意資源/操作/實例。
1、單個資源單個權(quán)限 Java代碼
用戶擁有資源“system:user”的“update”權(quán)限。
2、單個資源多個權(quán)限 ini配置文件 Java代碼
然后通過如下代碼判斷 Java代碼
用戶擁有資源“system:user”的“update”和“delete”權(quán)限。如上可以簡寫成:
ini配置(表示角色4擁有system:user資源的update和delete權(quán)限) Java代碼
接著可以通過如下代碼判斷 Java代碼
通過“system:user:update,delete”驗證"system:user:update, system:user:delete"是沒問題的,但是反過來是規(guī)則不成立。
3、單個資源全部權(quán)限 ini配置 Java代碼
然后通過如下代碼判斷 Java代碼
用戶擁有資源“system:user”的“create”、“update”、“delete”和“view”所有權(quán)限。如上可以簡寫成:
ini配置文件(表示角色5擁有system:user的所有權(quán)限) Java代碼
也可以簡寫為(推薦上邊的寫法): Java代碼
然后通過如下代碼判斷 Java代碼
通過“system:user:*”驗證“system:user:create,delete,update:view”可以,但是反過來是不成立的。
4、所有資源全部權(quán)限 ini配置 Java代碼
然后通過如下代碼判斷 Java代碼
用戶擁有所有資源的“view”所有權(quán)限。假設(shè)判斷的權(quán)限是“"system:user:view”,那么需要“role5=*:*:view”這樣寫才行。
5、實例級別的權(quán)限 5.1、單個實例單個權(quán)限 ini配置 Java代碼
對資源user的1實例擁有view權(quán)限。 然后通過如下代碼判斷 Java代碼
5.2、單個實例多個權(quán)限 ini配置 Java代碼
對資源user的1實例擁有update、delete權(quán)限。 然后通過如下代碼判斷 Java代碼
5.3、單個實例所有權(quán)限 ini配置 Java代碼
對資源user的1實例擁有所有權(quán)限。 然后通過如下代碼判斷 Java代碼
5.4、所有實例單個權(quán)限 ini配置 Java代碼
對資源user的1實例擁有所有權(quán)限。 然后通過如下代碼判斷 Java代碼
5.5、所有實例所有權(quán)限 ini配置 Java代碼
對資源user的1實例擁有所有權(quán)限。 然后通過如下代碼判斷 Java代碼
6、Shiro對權(quán)限字符串缺失部分的處理 如“user:view”等價于“user:view:*”;而“organization”等價于“organization:*”或者“organization:*:*”??梢赃@么理解,這種方式實現(xiàn)了前綴匹配。 另外如“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 如下兩種方式是等價的: Java代碼
因此沒什么必要的話使用字符串更方便。
8、性能問題 通配符匹配方式比字符串相等匹配來說是更復(fù)雜的,因此需要花費更長時間,但是一般系統(tǒng)的權(quán)限不會太多,且可以配合緩存來提供其性能,如果這樣性能還達不到要求我們可以實現(xiàn)位操作算法實現(xiàn)性能更好的權(quán)限匹配。另外實例級別的權(quán)限驗證如果數(shù)據(jù)量太大也不建議使用,可能造成查詢權(quán)限及匹配變慢??梢钥紤]比如在sql查詢時加上權(quán)限字符串之類的方式在查詢時就完成了權(quán)限匹配。
3.4 授權(quán)流程
流程如下: 1、首先調(diào)用Subject.isPermitted*/hasRole*接口,其會委托給SecurityManager,而SecurityManager接著會委托給Authorizer; 2、Authorizer是真正的授權(quán)者,如果我們調(diào)用如isPermitted(“user:view”),其首先會通過PermissionResolver把字符串轉(zhuǎn)換成相應(yīng)的Permission實例; 3、在進行授權(quán)之前,其會調(diào)用相應(yīng)的Realm獲取Subject相應(yīng)的角色/權(quán)限用于匹配傳入的角色/權(quán)限; 4、Authorizer會判斷Realm的角色/權(quán)限是否和傳入的匹配,如果有多個Realm,會委托給ModularRealmAuthorizer進行循環(huán)判斷,如果匹配如isPermitted*/hasRole*會返回true,否則返回false表示授權(quán)失敗。
ModularRealmAuthorizer進行多Realm匹配流程: 1、首先檢查相應(yīng)的Realm是否實現(xiàn)了實現(xiàn)了Authorizer; 2、如果實現(xiàn)了Authorizer,那么接著調(diào)用其相應(yīng)的isPermitted*/hasRole*接口進行匹配; 3、如果有一個Realm匹配那么將返回true,否則返回false。
如果Realm進行授權(quán)的話,應(yīng)該繼承AuthorizingRealm,其流程是: 1.1、如果調(diào)用hasRole*,則直接獲取AuthorizationInfo.getRoles()與傳入的角色比較即可; 1.2、首先如果調(diào)用如isPermitted(“user:view”),首先通過PermissionResolver將權(quán)限字符串轉(zhuǎn)換成相應(yīng)的Permission實例,默認使用WildcardPermissionResolver,即轉(zhuǎn)換為通配符的WildcardPermission; 2、通過AuthorizationInfo.getObjectPermissions()得到Permission實例集合;通過AuthorizationInfo. getStringPermissions()得到字符串集合并通過PermissionResolver解析為Permission實例;然后獲取用戶的角色,并通過RolePermissionResolver解析角色對應(yīng)的權(quán)限集合(默認沒有實現(xiàn),可以自己提供); 3、接著調(diào)用Permission. implies(Permission p)逐個與傳入的權(quán)限比較,如果有匹配的則返回true,否則false。
3.5 Authorizer、PermissionResolver及RolePermissionResolverAuthorizer的職責(zé)是進行授權(quán)(訪問控制),是Shiro API中授權(quán)核心的入口點,其提供了相應(yīng)的角色/權(quán)限判斷接口,具體請參考其Javadoc。SecurityManager繼承了Authorizer接口,且提供了ModularRealmAuthorizer用于多Realm時的授權(quán)匹配。PermissionResolver用于解析權(quán)限字符串到Permission實例,而RolePermissionResolver用于根據(jù)角色解析相應(yīng)的權(quán)限集合。
我們可以通過如下ini配置更改Authorizer實現(xiàn): Java代碼
對于ModularRealmAuthorizer,相應(yīng)的AuthorizingSecurityManager會在初始化完成后自動將相應(yīng)的realm設(shè)置進去,我們也可以通過調(diào)用其setRealms()方法進行設(shè)置。對于實現(xiàn)自己的authorizer可以參考ModularRealmAuthorizer實現(xiàn)即可,在此就不提供示例了。
設(shè)置ModularRealmAuthorizer的permissionResolver,其會自動設(shè)置到相應(yīng)的Realm上(其實現(xiàn)了PermissionResolverAware接口),如: Java代碼
設(shè)置ModularRealmAuthorizer的rolePermissionResolver,其會自動設(shè)置到相應(yīng)的Realm上(其實現(xiàn)了RolePermissionResolverAware接口),如: Java代碼
示例 1、ini配置(shiro-authorizer.ini) Java代碼
Java代碼
設(shè)置securityManager 的realms一定要放到最后,因為在調(diào)用SecurityManager.setRealms時會將realms設(shè)置給authorizer,并為各個Realm設(shè)置permissionResolver和rolePermissionResolver。另外,不能使用IniSecurityManagerFactory創(chuàng)建的IniRealm,因為其初始化順序的問題可能造成后續(xù)的初始化Permission造成影響。
2、定義BitAndWildPermissionResolver及BitPermission BitPermission用于實現(xiàn)位移方式的權(quán)限,如規(guī)則是: 權(quán)限字符串格式:+資源字符串+權(quán)限位+實例ID;以+開頭中間通過+分割;權(quán)限:0 表示所有權(quán)限;1 新增(二進制:0001)、2 修改(二進制:0010)、4 刪除(二進制:0100)、8 查看(二進制:1000);如 +user+10 表示對資源user擁有修改/查看權(quán)限。 Java代碼
Permission接口提供了boolean implies(Permission p)方法用于判斷權(quán)限匹配的; Java代碼
BitAndWildPermissionResolver實現(xiàn)了PermissionResolver接口,并根據(jù)權(quán)限字符串是否以“+”開頭來解析權(quán)限字符串為BitPermission或WildcardPermission。
3、定義MyRolePermissionResolver RolePermissionResolver用于根據(jù)角色字符串來解析得到權(quán)限集合。 Java代碼
此處的實現(xiàn)很簡單,如果用戶擁有role1,那么就返回一個“menu:*”的權(quán)限。
4、自定義Realm Java代碼
此時我們繼承AuthorizingRealm而不是實現(xiàn)Realm接口;推薦使用AuthorizingRealm,因為: AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token):表示獲取身份驗證信息; AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals):表示根據(jù)用戶身份獲取授權(quán)信息。 這種方式的好處是當(dāng)只需要身份驗證時只需要獲取身份驗證信息而不需要獲取授權(quán)信息。對于AuthenticationInfo和AuthorizationInfo請參考其Javadoc獲取相關(guān)接口信息。
另外我們可以使用JdbcRealm,需要做的操作如下: 1、執(zhí)行sql/ shiro-init-data.sql 插入相關(guān)的權(quán)限數(shù)據(jù); 2、使用shiro-jdbc-authorizer.ini配置文件,需要設(shè)置jdbcRealm.permissionsLookupEnabled 為true來開啟權(quán)限查詢。
此次還要注意就是不能把我們自定義的如“+user1+10”配置到INI配置文件,即使有IniRealm完成,因為IniRealm在new完成后就會解析這些權(quán)限字符串,默認使用了WildcardPermissionResolver完成,即此處是一個設(shè)計權(quán)限,如果采用生命周期(如使用初始化方法)的方式進行加載就可以解決我們自定義permissionResolver的問題。
5、測試用例 Java代碼
通過如上步驟可以實現(xiàn)自定義權(quán)限驗證了。另外因為不支持hasAnyRole/isPermittedAny這種方式的授權(quán),可以參考我的一篇《簡單shiro擴展實現(xiàn)NOT、AND、OR權(quán)限驗證 》進行簡單的擴展完成這個需求,在這篇文章中通過重寫AuthorizingRealm里的驗證邏輯實現(xiàn)的。
示例源代碼:https://github.com/zhangkaitao/shiro-example;可加群134755960探討Spring/Shiro技術(shù)。 |
|
|