Shiro -- (五) 受權

受權

  受權,也叫訪問控制,即在應用中控制誰能訪問哪些資源(如訪問頁面/編輯數據/頁面操做等)。在受權中需瞭解的幾個關鍵對象:主體(Subject)、資源(Resource)、權限(Permission)、角色(Role)。數據庫

主體
  主體,即訪問應用的用戶,在 Shiro 中使用 Subject 表明該用戶。用戶只有受權後才容許訪問相應的資源。編程

資源
  在應用中用戶能夠訪問的任何東西,好比訪問 JSP 頁面、查看/編輯某些數據、訪問某個業務方法、打印文本等等都是資源。用戶只要受權後才能訪問。安全

權限
  安全策略中的原子受權單位,經過權限咱們能夠表示在應用中用戶有沒有操做某個資源的權力。即權限表示在應用中用戶能不能訪問某個資源,如: 訪問用戶列表頁面
查看/新增/修改/刪除用戶數據(即不少時候都是 CRUD(增查改刪)式權限控制)
打印文檔等等。。。app

  如上能夠看出,權限表明了用戶有沒有操做某個資源的權利,即反映在某個資源上的操做允不容許,不反映誰去執行這個操做。因此後續還須要把權限賦予給用戶,即定義哪一個用戶容許在某個資源上作什麼操做(權限),Shiro 不會去作這件事情,而是由實現人員提供。ide

  Shiro 支持粗粒度權限(如用戶模塊的全部權限)和細粒度權限(操做某個用戶的權限,即實例級別的),後續部分介紹。post

角色
  角色表明了操做集合,能夠理解爲權限的集合,通常狀況下咱們會賦予用戶角色而不是權限,即這樣用戶能夠擁有一組權限,賦予權限時比較方便。典型的如:項目經理、技術總監、CTO、開發工程師等都是角色,不一樣的角色擁有一組不一樣的權限。測試

隱式角色
  即直接經過角色來驗證用戶有沒有操做權限,如在應用中 CTO、技術總監、開發工程師可使用打印機,假設某天不容許開發工程師使用打印機,此時須要從應用中刪除相應代碼;再如在應用中 CTO、技術總監能夠查看用戶、查看權限;忽然有一天不容許技術總監查看用戶、查看權限了,須要在相關代碼中把技術總監角色從判斷邏輯中刪除掉;即粒度是以角色爲單位進行訪問控制的,粒度較粗;若是進行修改可能形成多處代碼修改。ui

顯示角色
  在程序中經過權限控制誰能訪問某個資源,角色聚合一組權限集合;這樣假設哪一個角色不能訪問某個資源,只須要從角色表明的權限集合中移除便可;無須修改多處代碼;即粒度是以資源/實例爲單位的;粒度較細。spa

基於角色與基於資源的權限訪問控制(RBAC和RBAC新解)

  基於角色的權限訪問控制RBACrole-based access control)是以角色爲中心進行的訪問控制,也就是判斷主體subject是那個角色的方式進行權限訪問控制,是粗粒度的code

  基於資源的權限訪問控制RBACresource-based access control)是以資源爲中心進行的訪問控制,只須要爲角色添加權限就能夠

 區別:

    因爲基於角色的權限訪問控制的角色與權限每每是多對多的關係(好比admin角色能夠全部CURD的權限,部門經理角色有Retrieve權限,這就是多對多關係了),若是角色所對應的權限發生變化 ,那咱們所編寫的判斷邏輯就必須發生改變,可擴展性差

 好比:本來只有admin能夠訪問,那麼判斷能夠這麼寫

      if(role.equals(」admin」)){
        //retrieve
      }

  可是假設後期須要給部門經理角色也賦予retrieve權限,那麼必須改變原有代碼,或者另外增長代碼,總之要改變原有的判斷邏輯

  if(role.equals("admin") || role.equals("manager")){
      //retrieve    

      }

    若是是基於資源的權限訪問控制,資源和權限一對一關係比較常見,不少時候資源和權限在數據庫中會被合併在一張表中,只須要爲資源分配相應的權限。因此一個具體操做對應的權限,只要直接判斷用戶是否擁有該權限便可,可擴展性強

   //判斷用戶是否具備查看權限,用戶的角色能夠任意變化,而這條判斷語句始終是可行的
      if(user.hasPermission("retrieve")){

        //retrieve 

      }
  若是用戶的權限須要改變,只須要對數據庫中用戶的角色對應的權限進行改變,而權限與對應資源一般不會有改變的需求

 

受權方式:

Shiro 支持三種方式的受權:

編程式:經過寫 if/else 受權代碼塊完成:

Subject subject = SecurityUtils.getSubject();
if(subject.hasRole(「admin」)) {
    //有權限
} else {
    //無權限
}
註解式:經過在執行的 Java 方法上放置相應的註解完成:
@RequiresRoles("admin")
public void hello() {
    //有權限
}

沒有權限將拋出相應的異常;

JSP/GSP 標籤:在 JSP/GSP 頁面經過相應的標籤完成:

<shiro:hasRole name="admin">
<!— 有權限 —>
</shiro:hasRole>

 

Demo:

基於角色的訪問控制(隱式角色)

配置文件:

 

 

 測試:

public class TestRole {
    @Test
    public void t1(){
        Factory<SecurityManager> factory= new IniSecurityManagerFactory("classpath:shiro-role.ini");
        SecurityManager securityManager = factory.getInstance();
        SecurityUtils.setSecurityManager(securityManager);
        Subject subject = SecurityUtils.getSubject();

        UsernamePasswordToken token=new UsernamePasswordToken("lc","123");
        try {
            subject.login(token);
            System.out.println("用戶是否擁有此角色:"+subject.hasRole("user"));
            System.out.println("用戶是否擁有任意一個角色:"+subject.hasAllRoles(Arrays.asList("admin","root")));
            System.out.println("用戶受否擁有角色:"+Arrays.toString(subject.hasRoles(Arrays.asList("admin","root"))));
           
       subject.checkRole(
"admin"); subject.checkRoles("admin","r"); System.out.println(); }catch (AuthenticationException e){ System.out.println("失敗! "+e); } subject.logout(); } }

由配置文件知,lc用戶只有admin與root兩個角色,沒有user角色,因此是false,其餘兩個方法相似;checkRole檢查斷言角色,admin能夠,沒有r角色因此報錯。

 

 

 

 基於資源的訪問控制(顯示角色)

  配置文件:兩個用戶對應着本身的角色,角色對應着權限

root角色權限:select,update,insert,delete

admin角色權限:select,delete

user角色權限:select

 

 

 測試:

  由配置文件知cc用戶角色爲user,對應的權限爲select,lc用戶角色爲root,admin,權限爲select,update,insert,delete

@Test
    public void t2(){
        Factory<SecurityManager> factory=new IniSecurityManagerFactory("classpath:shiro-permission.ini");
        SecurityManager securityManager = factory.getInstance();
        SecurityUtils.setSecurityManager(securityManager);
        Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token
=new UsernamePasswordToken("cc","123"); UsernamePasswordToken token2=new UsernamePasswordToken("lc","123"); try { subject.login(token); System.out.println(subject.isPermitted("select")); System.out.println( subject.isPermittedAll("select","insert","delete")); subject.login(token2); System.out.println(subject.isPermitted("select")); System.out.println( subject.isPermittedAll("select","insert","delete"));
        
        subject.checkPermission("select");
      subject.checkPermissions("select","insert");
}catch (AuthenticationException e){ System.out.println("nono"+e); } subject.logout(); }

結果:

 

 

 

 

受權流程

  

 

 

 

  1. 首先調用 Subject.isPermitted*/hasRole*接口,其會委託給 SecurityManager,而 SecurityManager 接着會委託給 Authorizer;
  2. Authorizer 是真正的受權者,若是咱們調用如 isPermitted(「select」),其首先會經過 PermissionResolver 把字符串轉換成相應的 Permission 實例;
  3. 在進行受權以前,其會調用相應的 Realm 獲取 Subject 相應的角色/權限用於匹配傳入的角色/權限;
  4. Authorizer 會判斷 Realm 的角色/權限是否和傳入的匹配,若是有多個 Realm,會委託給 ModularRealmAuthorizer 進行循環判斷,若是匹配如 isPermitted*/hasRole* 會返回 true,不然返回 false 表示受權失敗。

 

 

 


經典權限系:

大體用到5張表:用戶表(UserInfo)、角色表(RoleInfo)、菜單表(MenuInfo)、用戶角色表(UserRole)、角色菜單表(RoleMenu)。

  各表的大致表結構以下:

  1、用戶表(UserInfo):Id、UserName、UserPwd

  2、角色表(RoleInfo):Id、RoleName

  3、菜單表(MenuInfo):Id、MenuName

  4、用戶角色表(UserRole):Id、UserId、RoleId

  5、角色菜單表(RoleMenu):Id、RoleId、MenuId

 

  最關鍵的地方是,某個用戶登陸時,如何查找該用戶的菜單權限?其實一條語句便可搞定:

  假如用戶的用戶名爲zhangsan,則他的菜單權限查詢以下:

  

Select m.Id,m.MenuName from MenuInfo m ,UserInfo u UserRole ur, RoleMenu rm Where m.Id = rm.MenuId and ur.RoleId = rm.RoleId and ur.UserId = u.Id and u.UserName = 'zhangsan'
相關文章
相關標籤/搜索