前言
本章我們來(lái)學(xué)習(xí)權(quán)限認(rèn)證,在開(kāi)始前我們先明白一些基本的概念。
主體(Subject):即訪問(wèn)應(yīng)用的用戶(hù),在Shiro中使用Subject代表該用戶(hù)。用戶(hù)只有授權(quán)后才允許訪問(wèn)相應(yīng)的資源。
資源(Resource):在應(yīng)用中用戶(hù)可以訪問(wèn)的任何東西,比如訪問(wèn)JSP頁(yè)面、查看/編輯某些數(shù)據(jù)、訪問(wèn)某個(gè)業(yè)務(wù)方法、打印文本等等都是資源。用戶(hù)只要授權(quán)后才能訪問(wèn)。
權(quán)限(Permission):安全策略中的原子授權(quán)單位,通過(guò)權(quán)限我們可以表示在應(yīng)用中用戶(hù)有沒(méi)有操作某個(gè)資源的權(quán)力。即權(quán)限表示在應(yīng)用中用戶(hù)能不能訪問(wèn)某個(gè)資源,如:訪問(wèn)用戶(hù)列表頁(yè)面、查看/新增/修改/刪除用戶(hù)數(shù)據(jù)(即很多時(shí)候都是CRUD(增查改刪)式權(quán)限控制)等等。
如上可以看出,權(quán)限代表了用戶(hù)有沒(méi)有操作某個(gè)資源的權(quán)利,即反映在某個(gè)資源上的操作允不允許,不反映誰(shuí)去執(zhí)行這個(gè)操作。所以后續(xù)還需要把權(quán)限賦予給用戶(hù),即定義哪個(gè)用戶(hù)允許在某個(gè)資源上做什么操作(權(quán)限),Shiro不會(huì)去做這件事情,而是由實(shí)現(xiàn)人員提供。
角色(Role):角色代表了操作集合,可以理解為權(quán)限的集合,一般情況下我們會(huì)賦予用戶(hù)角色而不是權(quán)限,即這樣用戶(hù)可以擁有一組權(quán)限,賦予權(quán)限時(shí)比較方便。典型的如:項(xiàng)目經(jīng)理、技術(shù)總監(jiān)、CTO、開(kāi)發(fā)工程師等都是角色,不同的角色擁有一組不同的權(quán)限。
隱試式角色:即直接通過(guò)角色來(lái)驗(yàn)證用戶(hù)有沒(méi)有操作權(quán)限,粒度較粗。
顯示角色:在程序中通過(guò)權(quán)限控制誰(shuí)能訪問(wèn)某個(gè)資源,角色聚合一組權(quán)限集合;這樣假設(shè)哪個(gè)角色不能訪問(wèn)某個(gè)資源,只需要從角色代表的權(quán)限集合中移除即可;無(wú)須修改多處代碼;即
粒度是以資源/實(shí)例為單位的;粒度較細(xì)。
請(qǐng)google搜索“RBAC”和“RBAC新解”分別了解“基于角色的訪問(wèn)控制”“基于資源的訪問(wèn)控制(Resource-Based Access Control)”。
3.1授權(quán)方式
Shiro支持三種授權(quán)方式:
-
編程式:通過(guò)寫(xiě)if/else授權(quán)代碼塊完成:
Subject subject = SecurityUtils.getSubject(); if(subject.hasRole(“admin”)) { //有權(quán)限 } else { //無(wú)權(quán)限 } -
注解式:通過(guò)在執(zhí)行的Java方法上放置相應(yīng)的注解完成:
@RequiresRoles("admin") public void hello() { //有權(quán)限 }沒(méi)有權(quán)限將拋出相應(yīng)異常。
-
JSP/GSP標(biāo)簽:在JSP/GSP頁(yè)面通過(guò)相應(yīng)的標(biāo)簽完成:
<shiro:hasRole name="admin"> <!— 有權(quán)限 —> </shiro:hasRole>后面會(huì)詳細(xì)介紹如何使用。
3.2授權(quán)
-
3.2.1 基于角色的訪問(wèn)控制(隱式角色)
-
在ini配置文件配置用戶(hù)擁有的角色(shiro-role.ini)
[users] zhang=123,role1,role2 wang=123,role1規(guī)則即:“用戶(hù)名=密碼,角色1,角色2”,如果需要在應(yīng)用中判斷用戶(hù)是否有相應(yīng)角色,就需要在相應(yīng)的Realm中返回角色信息,也就是說(shuō)Shiro不負(fù)責(zé)維護(hù)用戶(hù)-角色信息,需要應(yīng)用提供,Shiro只是提供相應(yīng)的接口方便驗(yàn)證,后續(xù)會(huì)介紹如何動(dòng)態(tài)的獲取用戶(hù)角色。
-
測(cè)試用例(com.zhaojun.shiro.chapter3.RoleTest.testHasRole())
@Test public void testHasRole(){ String username = "wang"; String password = "123"; //登錄 login("classpath:shiro-role.ini",username,password); //判斷是否擁有角色 log.info(username + "是否擁有角色role1:"+SecurityUtils.getSubject().hasRole("role1")); log.info(username + "是否擁有角色role2:"+SecurityUtils.getSubject().hasRole("role2")); log.info(username + "是否擁有角色role1與role2:"+ Arrays.toString(SecurityUtils.getSubject().hasRoles(Arrays.asList("role1","role2")))); }輸出日志:
21:45:02.389 [main] INFO com.zhaojun.shiro.chapter3.RoleTest - wang是否擁有角色role1:true 21:45:02.390 [main] INFO com.zhaojun.shiro.chapter3.RoleTest - wang是否擁有角色role2:false 21:45:02.391 [main] INFO com.zhaojun.shiro.chapter3.RoleTest - wang是否擁有角色role1與role2:[true, false]Shiro提供了hasRole/hasRole用于判斷用戶(hù)是否擁有某個(gè)角色/某些權(quán)限;但是沒(méi)有提供如hashAnyRole用于判斷是否有某些權(quán)限中的某一個(gè)。
@Test(expected = UnauthorizedException.class) public void testCheckRole(){ String username = "wang"; String password = "123"; //登錄 login("classpath:shiro-role.ini",username,password); //斷言擁有角色 SecurityUtils.getSubject().checkRole("role2"); SecurityUtils.getSubject().checkRoles("role1","role2"); }Shiro提供的checkRole/checkRoles和hasRole/hasAllRoles不同的地方是它在判斷為假的情況下會(huì)拋出UnauthorizedException異常。
到此基于角色的訪問(wèn)控制(即隱式角色)就完成了,這種方式的缺點(diǎn)就是如果很多地方進(jìn)行了角色判斷,但是有一天不需要了那么就需要修改相應(yīng)代碼把所有相關(guān)的地方進(jìn)行刪除;這就是粗粒度造成的問(wèn)題。
-
-
3.2.2基于資源的訪問(wèn)控制(顯示角色)
-
在ini配置文件配置用戶(hù)擁有的角色及角色-權(quán)限關(guān)系(shiro-permission.ini)
[users] zhang=123,role1,role2 wang=123,role1 [roles] role1=user:create,user:update roel2=user:create,user:delete -
測(cè)試用例(com.zhaojun.shiro.chapter3.PermissionTest.testPermission())
@Test public void testPermission(){ String username = "zhang"; String password = "123"; login("classpath:shiro-permission.ini",username,password); //判斷是否擁有權(quán)限 log.info(username + "是否擁有user:create權(quán)限:"+SecurityUtils.getSubject().isPermitted("user:create")); log.info(username+":"+SecurityUtils.getSubject().isPermittedAll("user:create","user:update")); log.info(username + "是否擁有user:delete權(quán)限:"+SecurityUtils.getSubject().isPermitted("user:delete")); }Shiro提供了isPermitted和isPermittedAll用于判斷用戶(hù)是否擁有某個(gè)權(quán)限或所有權(quán)限,也沒(méi)有提供如isPermittedAny用于判斷擁有某一個(gè)權(quán)限的接口。
@Test(expected = UnauthorizedException.class) public void testCheckPermission(){ login("classpath:shiro-permission.ini", "zhang", "123"); Subject subject = SecurityUtils.getSubject(); //斷言擁有權(quán)限:user:create subject.checkPermission("user:create"); //斷言擁有權(quán)限:user:delete and user:update subject.checkPermissions("user:delete", "user:update"); //斷言擁有權(quán)限:user:view 失敗拋出異常 subject.checkPermissions("user:view"); }與checkRole一樣,若沒(méi)有相應(yīng)權(quán)限會(huì)拋出UnauthorizedException異常。
到此基于資源的訪問(wèn)控制(顯示角色)就完成了,也可以叫基于權(quán)限的訪問(wèn)控制,這種方式的一般規(guī)則是“資源標(biāo)識(shí)符:操作”,即是資源級(jí)別的粒度;這種方式的好處就是如果要修改基本都是一個(gè)資源級(jí)別的修改,不會(huì)對(duì)其他模塊代碼產(chǎn)生影響,粒度小。但是實(shí)現(xiàn)起來(lái)可能稍微復(fù)雜點(diǎn),需要維護(hù)“用戶(hù)——角色,角色——權(quán)限(資源:操作)”之間的關(guān)系。
-
3.3 Permission
字符串權(quán)限通配
規(guī)則:“資源標(biāo)識(shí)符:操作:對(duì)象實(shí)例ID” 即對(duì)哪個(gè)資源的哪個(gè)實(shí)例可以進(jìn)行什么操作。其默認(rèn)支持通配符權(quán)限字符串,“:”表示資源/操作/實(shí)例的分割;“,”表示操作的分割;“*”表示任意資源/操作/實(shí)例。
-
3.3.1 單個(gè)資源單個(gè)權(quán)限
subject.checkPermissions("system:user:update");用戶(hù)擁有資源“system:user”的“update”權(quán)限。
-
3.3.2 單個(gè)資源多個(gè)權(quán)限
role41=system:user:update,system:user:delete然后通過(guò)如下代碼判斷
subject.checkPermissions("system:user:update", "system:user:delete");用戶(hù)擁有資源“system:user”的“update”和“delete”權(quán)限。如上可以簡(jiǎn)寫(xiě)成:
role42="system:user:update,delete"接著可以通過(guò)如下代碼判斷
subject.checkPermissions("system:user:update,delete");通過(guò)“system:user:update,delete”驗(yàn)證"system:user:update, system:user:delete"是沒(méi)問(wèn)題的,但是反過(guò)來(lái)是規(guī)則不成立。
-
3.3.3 單個(gè)資源全部權(quán)限
role51="system:user:create,update,delete,view"然后通過(guò)如下代碼判斷
subject.checkPermissions("system:user:create,delete,update:view");用戶(hù)擁有資源“system:user”的“create”、“update”、“delete”和“view”所有權(quán)限。如上可以簡(jiǎn)寫(xiě)成:
role52=system:user:*表示角色52擁有system:user的所有權(quán)限。如上可以簡(jiǎn)寫(xiě)成(推薦上面的寫(xiě)法):
role53=system:user然后通過(guò)如下代碼判斷
subject.checkPermissions("system:user:*"); subject.checkPermissions("system:user");通過(guò)“system:user:*”驗(yàn)證“system:user:create,delete,update:view”可以,但是反過(guò)來(lái)是不成立的。
-
3.3.4 所有資源全部權(quán)限
role61=*:view然后通過(guò)如下代碼判斷:
subject.checkPermissions("user:view");用戶(hù)擁有所有資源的“view”所有權(quán)限。假設(shè)判斷的權(quán)限是“"system:user:view”,那么需要“role5=::view”這樣寫(xiě)才行。
-
3.3.5實(shí)例級(jí)別的權(quán)限
-
單個(gè)實(shí)例權(quán)限
role71=user:view:1對(duì)資源user的1實(shí)例擁有view權(quán)限。
然后通過(guò)如下代碼判斷
subject.checkPermissions("user:view:1"); -
單個(gè)實(shí)例多個(gè)權(quán)限
role72="user:update,delete:1"對(duì)資源user的1實(shí)例擁有update、delete權(quán)限。
然后通過(guò)如下代碼判斷
ubject.checkPermissions("user:delete,update:1"); subject.checkPermissions("user:update:1", "user:delete:1"); -
單個(gè)實(shí)例所有權(quán)限
role73=user:*:1對(duì)資源user的1實(shí)例擁有所有權(quán)限。
然后通過(guò)如下代碼判斷 :
subject.checkPermissions("user:auth:1"); -
所有實(shí)例所有權(quán)限
role74=user:*:*對(duì)資源user的1實(shí)例擁有所有權(quán)限。
然后通過(guò)如下代碼判斷 :
subject.checkPermissions("user:view:1", "user:auth:2");?
-
-
3.3.6 Shiro 對(duì)權(quán)限字符串缺失部分的處理
如“user:view”等價(jià)于“user:view:”;而“organization”等價(jià)于“organization:”或者“organization::”??梢赃@么理解,這種方式實(shí)現(xiàn)了前綴匹配。
另外如“user:”可以匹配如“user:delete”、“user:delete”可以匹配如“user:delete:1”、“user::1”可以匹配如“user:view:1”、“user”可以匹配“user:view”或“user:view:1”等。即可以匹配所有,不加可以進(jìn)行前綴匹配;但是如“:view”不能匹配“system:user:view”,需要使用“::view”,即后綴匹配必須指定前綴(多個(gè)冒號(hào)就需要多個(gè)來(lái)匹配)。
-
3.3.7WildcardPermission
如下兩種方式是等價(jià)的:
subject.checkPermission("menu:view:1"); subject.checkPermission(new WildcardPermission("menu:view:1"));因此沒(méi)什么必要的話(huà)使用字符串更方便。
?
3.4授權(quán)流程

流程如下:
1、首先調(diào)用Subject.isPermitted/hasRole接口,其會(huì)委托給SecurityManager,而SecurityManager接著會(huì)委托給Authorizer;
2、Authorizer是真正的授權(quán)者,如果我們調(diào)用如isPermitted(“user:view”),其首先會(huì)通過(guò)PermissionResolver把字符串轉(zhuǎn)換成相應(yīng)的Permission實(shí)例;
3、在進(jìn)行授權(quán)之前,其會(huì)調(diào)用相應(yīng)的Realm獲取Subject相應(yīng)的角色/權(quán)限用于匹配傳入的角色/權(quán)限;
4、Authorizer會(huì)判斷Realm的角色/權(quán)限是否和傳入的匹配,如果有多個(gè)Realm,會(huì)委托給ModularRealmAuthorizer進(jìn)行循環(huán)判斷,如果匹配如isPermitted/hasRole會(huì)返回true,否則返回false表示授權(quán)失敗。
ModularRealmAuthorizer進(jìn)行多Realm匹配流程:
1、首先檢查相應(yīng)的Realm是否實(shí)現(xiàn)了實(shí)現(xiàn)了Authorizer;
2、如果實(shí)現(xiàn)了Authorizer,那么接著調(diào)用其相應(yīng)的isPermitted/hasRole接口進(jìn)行匹配;
3、如果有一個(gè)Realm匹配那么將返回true,否則返回false。
如果Realm進(jìn)行授權(quán)的話(huà),應(yīng)該繼承AuthorizingRealm,其流程是:
1.1、如果調(diào)用hasRole*,則直接獲取AuthorizationInfo.getRoles()與傳入的角色比較即可;
1.2、首先如果調(diào)用如isPermitted(“user:view”),首先通過(guò)PermissionResolver將權(quán)限字符串轉(zhuǎn)換成相應(yīng)的Permission實(shí)例,默認(rèn)使用WildcardPermissionResolver,即轉(zhuǎn)換為通配符的WildcardPermission;
2、通過(guò)AuthorizationInfo.getObjectPermissions()得到Permission實(shí)例集合;通過(guò)AuthorizationInfo. getStringPermissions()得到字符串集合并通過(guò)PermissionResolver解析為Permission實(shí)例;然后獲取用戶(hù)的角色,并通過(guò)RolePermissionResolver解析角色對(duì)應(yīng)的權(quán)限集合(默認(rèn)沒(méi)有實(shí)現(xiàn),可以自己提供);
3、接著調(diào)用Permission. implies(Permission p)逐個(gè)與傳入的權(quán)限比較,如果有匹配的則返回true,否則false。
張開(kāi)濤的博客:http://jinnianshilongnian.iteye.com/category/305053