《跟我學(xué)Shiro》學(xué)習(xí)筆記 第三章:授權(quán)

前言

本章我們來(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)控制(隱式角色)

    1. 在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ù)角色。

    2. 測(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)控制(顯示角色)

    1. 在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
      
    2. 測(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)限

    1. 單個(gè)實(shí)例權(quán)限

      role71=user:view:1  
      

      對(duì)資源user的1實(shí)例擁有view權(quán)限。

      然后通過(guò)如下代碼判斷

      subject.checkPermissions("user:view:1");  
      
    2. 單個(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"); 
      
    3. 單個(gè)實(shí)例所有權(quán)限

      role73=user:*:1  
      

      對(duì)資源user的1實(shí)例擁有所有權(quán)限。

      然后通過(guò)如下代碼判斷 :

      subject.checkPermissions("user:auth:1");  
      
    4. 所有實(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)流程

授權(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

我的博客:https://zhaojun0193.github.io

本文代碼地址:https://github.com/zhaojun0193/shiro-example

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容