shiro學習和使用實例(3)——鑑權

一、shiro授權基礎概念

  (1)基於角色的訪問控制

    Shiro 提供了hasRole/hasRole 用於判斷用戶是否擁有某個角色/某些權限:
     subject().hasRole("role1");//判斷擁有角色:role1
     subject().hasAllRoles(Arrays.asList("role1", "role2"));
     subject().hasRoles(Arrays.asList("role1", "role2", "role3"));

     Shiro 提供的checkRole/checkRoles 和hasRole/hasAllRoles 不同的地方是它在判斷爲假的情況下會拋出UnauthorizedException異常。

記住一點,shiro不提供角色和權限的維護,如果程序其他地方要用到權限或角色信息,或者如果需要在應用中判斷用戶是否有相應角色,用戶是否有權限,就需要在相應的Realm中返回角色、權限信息,所有的信息都可以通過SecurityUtils.getSubject()來獲取,包括session,host

     (2)基於權限的訪問控制
     subject().isPermitted("user:create");//判斷擁有權限:user:create
     subject().isPermittedAll("user:update", "user:delete");//判斷擁有權限:user:update and user:delete
     subject().isPermitted("user:view");//判斷沒有權限:user:view

     Shiro 提供了isPermitted 和isPermittedAll 用於判斷用戶是否擁有某個權限或所有權限,但失敗的話,會拋出UnauthorizedException異常。

     (3)權限的格式
     “資源標識符:操作:對象實例ID” 即對哪個資源的哪個實例可以進行什麼操作。其默認支持通配符權限字符串,“:”表示資源/操作/實例的分割;“,”表示操作的分割;“*”表示任意資源/操作/實例。

     實際上還是不需要到實例級別的控制,如果到實例級別,數據量太大,會下降其性能。在本篇文章的鑑權實例中,權限的格式爲“資源標識符:操作”

     舉個例子:
     eg1:    system:user:update,system:user:insert
                   這個表示用戶擁有資源“system:user”的“update”和“insert”權限。
     鑑權判斷:
                   subject().checkPermissions("system:user:update", "system:user:insert");
                   subject().checkPermissions("system:user:update,insert");    //作用同上
     注意:通過“system:user:update,insert”驗證"system:user:update, system:user:insert"是沒問題的,但是反過來是規則不成立。
    
     eg2:   用戶擁有資源“system:user”的“create”、“update”、“delete”和“view”所有權限。
                   system:user:*
                   subject().checkPermissions("system:user:*");

     eg3:     對資源user的1 實例擁有view權限。
                     user:view:1
                     subject().checkPermissions("user:view:1");


二、shiro鑑權實例

前提:

      緊接着上一篇文章shiro學習和使用實例(2)——登陸認證和授權,登陸認證通過後,清除掉原來的權限緩存,加上當前登陸賬號的最新權限。接着就是登陸成功跳轉到index(首頁),此時,我們要對當前登陸賬號鑑權。

思路:

      賬號登陸成功跳轉到首頁時,我們要對當前賬號鑑權,通過判斷賬號是否具有某項操作的權限,來決定是否對當前登陸賬號顯示該操作的頁面菜單、按鈕或鏈接。當我們加載首頁的時候,通過js將頁面所有菜單的標識、權限發送到服務端,服務端調用shiro的isPermitted(String permission)從緩存中獲取當前賬號的權限信息(登陸成功後已將當前賬號的最新權限信息放到緩存中了),如果緩存中沒有,shiro會遠程從數據庫中獲取。isPermitted(String permission)把從緩存中獲取的權限和頁面發送來的權限進行比較,將有無權限的結果返回到頁面,通過回調函數決定菜單是否顯示(有權限顯示,無權限不顯示)。

      這是一種方案,也是我們接下來實例的內容。其實,還有粒度更小的權限限制方案,即對每個請求的方法加上權限判斷,登陸賬號具有這個權限,就可以請求此方法,得到響應,否則拒絕該請求。這個可以用shiro提供的註解來實現鑑權。


(1)、頁面請求

      首先要給所有的頁面菜單唯一的資源標識,然後就是該菜單的操作(veiw,insert,update,delete等),這樣就滿足了shiro的權限格式。例如系統中有“基礎數據管理”這個菜單,則“com.isoftstone.yyp.portal.om.basic.data:veiw”。注意,這裏的資源標識和操作要和授權時,加載到緩存中的權限要一致,也就是說,數據庫保存的權限信息也必須是這個格式。

      下面我們來看頁面js的代碼:

[javascript] view plain copy
  1. $.permissionFilter(  
  2.                     [  
  3.                             {  
  4.                                 permission : "view",  
  5.                                 depBundle_SymbolicName : "com.isoftstone.yyp.portal.om.member.role",  
  6.                                 display : function(status) {  
  7.                                     if (status == 0) {//0:有權限,1:無權限  
  8.                                         $("#hymember,#member_role").show();  
  9.                                         permission.memberPermissoin = true;  
  10.                                     } else {  
  11.                                         $("#member_role").remove();  
  12.                                     }  
  13.                                 }  
  14.                             },  
  15.                             {   
  16.                                 permission : "view",  
  17.                                 depBundle_SymbolicName : "com.isoftstone.yyp.portal.om.member.query",  
  18.                                 display : function(status) {  
  19.                                     if (status == 0) {//0:有權限,1:無權限  
  20.                                         $("#hymember,#member_query").show();  
  21.                                         permission.memberPermissoin = true;  
  22.                                     } else {  
  23.                                         $("#member_query").remove();  
  24.                                     }  
  25.                                 }  
  26.                             }  
  27.                                            ], "",  
  28.                     "../../mvc/oBtnLinkController/getPermissions.json");  
permission:操作

depBundle_SymbolicName:資源標識

display:回調函數,用於菜單顯示

這裏只保留了兩個菜單的權限信息,如果有更多,按這種格式加上接下來是js權限公共方法:

[javascript] view plain copy
  1. (function($){  
  2.     var options = {  
  3.             guid : function(){  
  4.                 var guid = new Date().getTime(), i;  
  5.                 for (i = 0; i < 5; i++) {  
  6.                     guid += Math.floor(Math.random() * 65535);  
  7.                 }  
  8.                 return  guid ;  
  9.             },  
  10.             display : function(status,obj,o){ //默認處理邏輯  針對界面的CRUD按鈕進行操作,其中R(查詢)按鈕對應類型FORM,由於框架一定會生成,並且查詢和菜單權限綁定,暫時不作處理  
  11.                 switch(obj.type){  
  12.                     case 'ADDBUTTON':  
  13.                         if(status == 1){  
  14.                             o.grid.addable = false;  
  15.                          }else if(status == 0){  
  16.                             o.grid.addable = true;  
  17.                          }  
  18.                         break;  
  19.                     case 'EDITBUTTON':  
  20.                          if(status == 1){  
  21.                              o.grid.updateable = false;  
  22.                          }else if(status == 0){  
  23.                              o.grid.updateable = true;  
  24.                          }  
  25.                         break;  
  26.                     case 'REMOVEBUTTON':  
  27.                          if(status == 1 ){  
  28.                             o.grid.deleteable = false;  
  29.                          }else if(status == 0){  
  30.                             o.grid.deleteable = true;  
  31.                          }  
  32.                         break;  
  33.                     case 'FORM':  
  34.                         if(status == '1'){  
  35.                             //$("."+obj.id+"").remove();  
  36.                         }  
  37.                         break;  
  38.             }  
  39.         }  
  40.     };  
  41.   
  42.     /** 
  43.      * 限定三個參數: 
  44.      *      權限控制數組、 
  45.      *      本bundle的SymbolicNames、 
  46.      *      傳遞給CRUD框架參數(可選) 
  47.      * 其中權限控制數組對象格式爲 
  48.      *      {permission:"",type:"",depBundle_SymbolicName:"",display:function:{}} 
  49.      *          type和display 選擇其一 
  50.      *          depBundle_SymbolicName爲空,則默認爲 方法第二個參數【本bundle的SymbolicNames】 
  51.      */  
  52.     $.permissionFilter = function(){  
  53.         var elements = null;  
  54.         if((arguments.length == 3 ||arguments.length == 2) && typeof arguments[0] == "object"){  
  55.             elements = arguments[0];  
  56.         }else{  
  57.             return;  
  58.         }  
  59.         var belongbundle_SymbolicName = arguments[1];//權限所屬於模板標識,單個  
  60.         var url = arguments[2];//傳遞給CRUD框架參數  
  61.         var permissions = "";//權限標識,  
  62.         var bundle_SymbolicNames = "";//權限所屬於模板標識,數組,  
  63.         var ids = ""//  
  64.         $.each(elements,function(i,e){  
  65.             if(e){  
  66.                 permissions += (e.permission?e.permission:"nopermission") + ",";  
  67.                 e.id = (e.id?e.id:options.guid());  
  68.                 ids +=  e.id + ",";  
  69.                 bundle_SymbolicNames += (e.depBundle_SymbolicName?e.depBundle_SymbolicName:belongbundle_SymbolicName) + ",";  
  70.             }  
  71.         });  
  72.         if(!url){  
  73.             url="../../mvc/btnLinkController/getPermissions.json";  
  74.         }  
  75.         permissions = permissions.substr(0,permissions.length-1);  
  76.         ids = ids.substr(0,ids.length-1);  
  77.         bundle_SymbolicNames = bundle_SymbolicNames.substr(0,bundle_SymbolicNames.length-1);  
  78.         $.ajax({  
  79.             url:url,  
  80.             dataType:"json",  
  81.             data:{"permissions":permissions,"bundle_SymbolicNames":bundle_SymbolicNames,"belongbundle_SymbolicName":belongbundle_SymbolicName,"ids":ids},  
  82.             async:false,  
  83.             type:'post',  
  84.             success:function(result){  
  85.                 for(var j =0; j<result.length; j++){  
  86.                     var element = result[j];  
  87.                     for(var i=0; i<elements.length; i++){  
  88.                         if(elements[i].id == element.id){  
  89.                             if(elements[i].display){//沒有定義type類型,就必須定義display方法  
  90.                                 elements[i].display(element.display);  
  91.                             }else{  
  92.                                                               //options.display(element.display,elements[i],optionsObj)  
  93.                             }  
  94.                             break;  
  95.                         }  
  96.                     }  
  97.                 }  
  98.             }  
  99.         });  
  100.     }  
  101. })(jQuery);  
這個公共方法就是封裝參數,向服務端請求,等到響應,執行回調函數顯示或者不顯示菜單。

(2)服務端鑑權

先上代碼,一看就明白

  1.       @Override  
  2. ublic List<HTMLElement> getPermissions(List<String> permissions,String []bundle_SymbolicNames,String belongbundle_SymbolicName,List<String> ids) {  
  3. if(permissions == null || bundle_SymbolicNames == null){  
  4.     return null;  
  5. }  
  6. if(bundle_SymbolicNames.length != permissions.size() ||( permissions.size() != ids.size())){  
  7.     return null;  
  8. }  
  9. List<HTMLElement> elements = new ArrayList<HTMLElement>();        
  10. Subject currentUser = SecurityUtils.getSubject();    
  11. for(int i=0; i<permissions.size(); i++){  
  12.     boolean exist = false;  
  13.     String permissionName = bundle_SymbolicNames[i].concat(":").concat(permissions.get(i));  
  14.     //驗證是否有權限  
  15.     if(currentUser.isPermitted(permissionName)){  
  16.         exist = true;  
  17.         HTMLElement element = new HTMLElement(ids.get(i),0);  
  18.         elements.add(element);  
  19.         }  
  20.     if(!exist){  
  21.         HTMLElement element = new HTMLElement(ids.get(i),1);  
  22.         elements.add(element);  
  23.     }  
  24. }  
  25. return elements;  
      服務端將頁面傳來的權限信息和緩存中的權限信息比較,有權限返回0,否則返回1,頁面js的回調函數根據這個狀態來決定是否顯示菜單。

      這裏要重點說明一下shiro的isPermitted方法。

  1. public boolean isPermitted(PrincipalCollection principals, String permission) {  
  2.     Permission p = getPermissionResolver().resolvePermission(permission);  
  3.     return isPermitted(principals, p);  
  4. }  
  5.   
  6. public boolean isPermitted(PrincipalCollection principals, Permission permission) {  
  7.     AuthorizationInfo info = getAuthorizationInfo(principals);  
  8.     return isPermitted(permission, info);  
  9. }  
      根據shiro的源碼,isPermitted會先將傳進來的權限字符串轉化爲shiro的Permission實例;接着回去獲取授權信息AuthorzationInfo;獲取授權信息時,它是先從緩存中取,如果緩存中沒有,會調用doGetAuthorizationInfo(principals)獲取。doGetAuthorizationInfo(principals)這個方法就是我們之前再自定義realm域中重寫的授權方法,詳情見shiro學習和使用實例(2)——登陸認證和授權中的授權。下面是getAuthorizationInfo方法
  1. protected AuthorizationInfo getAuthorizationInfo(PrincipalCollection principals) {  
  2.   
  3.     if (principals == null) {  
  4.         return null;  
  5.     }  
  6.   
  7.     AuthorizationInfo info = null;  
  8.   
  9.     if (log.isTraceEnabled()) {  
  10.         log.trace("Retrieving AuthorizationInfo for principals [" + principals + "]");  
  11.     }  
  12.   
  13.     Cache<Object, AuthorizationInfo> cache = getAvailableAuthorizationCache();  
  14.     if (cache != null) {  
  15.         if (log.isTraceEnabled()) {  
  16.             log.trace("Attempting to retrieve the AuthorizationInfo from cache.");  
  17.         }  
  18.         Object key = getAuthorizationCacheKey(principals);  
  19.         info = cache.get(key);  
  20.         if (log.isTraceEnabled()) {  
  21.             if (info == null) {  
  22.                 log.trace("No AuthorizationInfo found in cache for principals [" + principals + "]");  
  23.             } else {  
  24.                 log.trace("AuthorizationInfo found in cache for principals [" + principals + "]");  
  25.             }  
  26.         }  
  27.     }  
  28.     if (info == null) {  
  29.         // Call template method if the info was not found in a cache  
  30.         info = doGetAuthorizationInfo(principals);  
  31.         // If the info is not null and the cache has been created, then cache the authorization info.  
  32.         if (info != null && cache != null) {  
  33.             if (log.isTraceEnabled()) {  
  34.                 log.trace("Caching authorization info for principals: [" + principals + "].");  
  35.             }  
  36.             Object key = getAuthorizationCacheKey(principals);  
  37.             cache.put(key, info);  
  38.         }  
  39.     }  
  40.   
  41.     return info;  

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章