授權,也叫訪問控制,即在應用中控制誰能訪問哪些資源(如訪問頁面/編輯數據/頁面操作等)。
在權限認證中,最核心的是:主體/用戶(Subject)、權限(Permission)、角色(Role)、資源(Resource)。
1、權限,即操作資源的權利,通過權限我們可以表示在應用中用戶有沒有操作某個資源的權力。即權限表示在應用中用戶能不能訪問某個資源,比如訪問某個頁面,以及對某個模塊的數據的添加,修改,刪除,查看的權利;
可以看出,權限代表了用戶有沒有操作某個資源的權利,即反映在某個資源上的操作允不允許,不反映誰去執行這個操作。所以后續還需要把權限賦予給用戶,即定義哪個用戶允許在某個資源上做什么操作(權限),Shiro不會去做這件事情,而是由實現人員提供。
Shiro支持粗粒度權限(如用戶模塊的所有權限)和細粒度權限(操作某個用戶的權限,即實例級別的),
2、角色,是權限的集合,一中角色可以包含多種權限;
角色代表了操作集合,可以理解為權限的集合,一般情況下我們會賦予用戶角色而不是權限,即這樣用戶可以擁有一組權限,賦予權限時比較方便。典型的如:項目經理、技術總監、CTO、開發工程師等都是角色,不同的角色擁有一組不同的權限。
隱式角色:即直接通過角色來驗證用戶有沒有操作權限,如在應用中CTO、技術總監、開發工程師可以使用打印機,假設某天不允許開發工程師使用打印機,此時需要從應用中刪除相應代碼;再如在應用中CTO、技術總監可以查看用戶、查看權限;突然有一天不允許技術總監查看用戶、查看權限了,需要在相關代碼中把技術總監角色從判斷邏輯中刪除掉;即粒度是以角色為單位進行訪問控制的,粒度較粗;如果進行修改可能造成多處代碼修改。
顯示角色:在程序中通過權限控制誰能訪問某個資源,角色聚合一組權限集合;這樣假設哪個角色不能訪問某個資源,只需要從角色代表的權限集合中移除即可;無須修改多處代碼;即粒度是以資源/實例為單位的;粒度較細。
3、主體,即訪問應用的用戶,在Shiro中使用Subject代表該用戶。用戶只有授權后才允許訪問相應的資源。
4、資源:在應用中用戶可以訪問的任何東西,比如訪問JSP頁面、查看/編輯某些數據、訪問某個業務方法、打印文本等等都是資源。用戶只要授權后才能訪問。
一、授權
1,編程式授權:通過寫if/else授權代碼塊完成
1.1 基於角色的訪問控制
1.2 基於權限的訪問控制
3,Jsp 標簽授權:在JSP/GSP頁面通過相應的標簽完成:
1-1.編程式授權
1-1-1.基於角色的訪問控制(隱式角色)
1、在ini配置文件配置用戶擁有的角色(shiro-role.ini)
Shiro提供了hasRole/hasRole用於判斷用戶是否擁有某個角色/某些權限;但是沒有提供如hashAnyRole用於判斷是否有某些權限中的某一個。
Shiro提供的checkRole/checkRoles和hasRole/hasAllRoles不同的地方是它在判斷為假的情況下會拋出UnauthorizedException異常。
到此基於角色的訪問控制(即隱式角色)就完成了,這種方式的 缺點 就是如果很多地方進行了角色判斷,但是有一天不需要了那么就需要修改相應代碼把所有相關的地方進行刪除;這就是粗粒度造成的問題。
1-1-2 基於權限的訪問控制(顯示角色)
1、在ini配置文件配置用戶擁有的角色及角色-權限關系(shiro-permission.ini)
Shiro提供了isPermitted和isPermittedAll用於判斷用戶是否擁有某個權限或所有權限,使用checkPermission用於判斷擁有某一個權限的接口
到此基於資源的訪問控制(顯示角色)就完成了,也可以叫基於權限的訪問控制,這種方式的一般規則是“資源標識符:操作”,即是資源級別的粒度;這種方式的好處就是如果要修改基本都是一個資源級別的修改,不會對其他模塊代碼產生影響,粒度小。但是實現起來可能稍微復雜點,需要維護“用戶——角色,角色——權限(資源:操作)”之間的關系。
二、Permission對權限深入理解
單個權限 query單個資源多個權限 user:query user:add 多值 user:query,add
單個資源所有權限 user:query,add,update,delete user:*
所有資源某個權限 *:view
實例級別的權限控制
單個實例的單個權限 printer:query:lp7200 printer:print:epsoncolor
所有實例的單個權限 printer:print:*
所有實例的所有權限 printer:*:*
單個實例的所有權限 printer:*:lp7200
單個實例的多個權限 printer:query,print:lp7200
字符串通配符權限
規則:“資源標識符:操作:對象實例ID” 即對哪個資源的哪個實例可以進行什么操作。其默認支持通配符權限字符串,“:”表示資源/操作/實例的分割;“,”表示操作的分割;“*”表示任意資源/操作/實例。
1、單個資源單個權限
currentUser.checkPermissions("system:user:update");
用戶擁有資源“system:user”的“update”權限。
2、單個資源多個權限
ini配置文件
role41=system:user:update,system:user:delete
然后通過如下代碼判斷
subject().checkPermissions("system:user:update", "system:user:delete");
用戶擁有資源“system:user”的“update”和“delete”權限。如上可以簡寫成:
ini配置(表示角色4擁有system:user資源的update和delete權限)
role42="system:user:update,delete"
接着可以通過如下代碼判斷
subject().checkPermissions("system:user:update,delete");
通過“system:user:update,delete”驗證"system:user:update, system:user:delete"是沒問題的,但是反過來是規則不成立。
3、單個資源全部權限
ini配置
role51="system:user:create,update,delete,view"
然后通過如下代碼判斷
subject().checkPermissions("system:user:create,delete,update:view");
用戶擁有資源“system:user”的“create”、“update”、“delete”和“view”所有權限。如上可以簡寫成:
ini配置文件(表示角色5擁有system:user的所有權限)
role52=system:user:*
也可以簡寫為(推薦上邊的寫法):
role53=system:user
然后通過如下代碼判斷
subject().checkPermissions("system:user:*");
subject().checkPermissions("system:user");
通過“system:user:*”驗證“system:user:create,delete,update:view”可以,但是反過來是不成立的。
4、所有資源全部權限
ini配置
role61=*:view
然后通過如下代碼判斷
subject().checkPermissions("user:view");
用戶擁有所有資源的“view”所有權限。假設判斷的權限是“"system:user:view”,那么需要“role5=*:*:view”這樣寫才行。
5、實例級別的權限
5.1、單個實例單個權限
ini配置
role71=user:view:1
對資源user的1實例擁有view權限。
然后通過如下代碼判斷
subject().checkPermissions("user:view:1");
5.2、單個實例多個權限
ini配置
role72="user:update,delete:1"
對資源user的1實例擁有update、delete權限。
然后通過如下代碼判斷
subject().checkPermissions("user:delete,update:1");
subject().checkPermissions("user:update:1", "user:delete:1");
5.3、單個實例所有權限
ini配置
role73=user:*:1
對資源user的1實例擁有所有權限。
然后通過如下代碼判斷
subject().checkPermissions("user:update:1", "user:delete:1", "user:view:1");
5.4、所有實例單個權限
ini配置
role74=user:auth:*
對資源user的1實例擁有所有權限。
然后通過如下代碼判斷
subject().checkPermissions("user:auth:1", "user:auth:2");
5.5、所有實例所有權限
ini配置
role75=user:*:*
對資源user的1實例擁有所有權限。
然后通過如下代碼判斷
subject().checkPermissions("user:view:1", "user:auth:2");
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。