授權 授權,也叫訪問控制,即在應用中控制誰能訪問哪些資源(如訪問頁面/編輯數據/頁面操作等)。在授權中需瞭解的幾個關鍵對象:主體(Subject)、資源(Resource)、許可權(Permission)、角色(Role)。 主體 主體,即訪問應用的用戶,在 Shiro 中使用 Subject 代表該 ...
授權
授權,也叫訪問控制,即在應用中控制誰能訪問哪些資源(如訪問頁面/編輯數據/頁面操作等)。在授權中需瞭解的幾個關鍵對象:主體(Subject)、資源(Resource)、許可權(Permission)、角色(Role)。
主體
主體,即訪問應用的用戶,在 Shiro 中使用 Subject 代表該用戶。用戶只有授權後才允許訪問相應的資源。
資源
在應用中用戶可以訪問的任何東西,比如訪問 JSP 頁面、查看/編輯某些數據、訪問某個業務方法、列印文本等等都是資源。用戶只要授權後才能訪問。
許可權
安全策略中的原子授權單位,通過許可權我們可以表示在應用中用戶有沒有操作某個資源的權力。即許可權表示在應用中用戶能不能訪問某個資源,如: 訪問用戶列表頁面
查看/新增/修改/刪除用戶數據(即很多時候都是 CRUD(增查改刪)式許可權控制)
列印文檔等等。。。
如上可以看出,許可權代表了用戶有沒有操作某個資源的權利,即反映在某個資源上的操作允不允許,不反映誰去執行這個操作。所以後續還需要把許可權賦予給用戶,即定義哪個用戶允許在某個資源上做什麼操作(許可權),Shiro 不會去做這件事情,而是由實現人員提供。
Shiro 支持粗粒度許可權(如用戶模塊的所有許可權)和細粒度許可權(操作某個用戶的許可權,即實例級別的),後續部分介紹。
角色
角色代表了操作集合,可以理解為許可權的集合,一般情況下我們會賦予用戶角色而不是許可權,即這樣用戶可以擁有一組許可權,賦予許可權時比較方便。典型的如:項目經理、技術總監、CTO、開發工程師等都是角色,不同的角色擁有一組不同的許可權。
隱式角色:
即直接通過角色來驗證用戶有沒有操作許可權,如在應用中 CTO、技術總監、開發工程師可以使用印表機,假設某天不允許開發工程師使用印表機,此時需要從應用中刪除相應代碼;再如在應用中 CTO、技術總監可以查看用戶、查看許可權;突然有一天不允許技術總監查看用戶、查看許可權了,需要在相關代碼中把技術總監角色從判斷邏輯中刪除掉;即粒度是以角色為單位進行訪問控制的,粒度較粗;如果進行修改可能造成多處代碼修改。
顯示角色:
在程式中通過許可權控制誰能訪問某個資源,角色聚合一組許可權集合;這樣假設哪個角色不能訪問某個資源,只需要從角色代表的許可權集合中移除即可;無須修改多處代碼;即粒度是以資源/實例為單位的;粒度較細。
基於角色與基於資源的許可權訪問控制(RBAC和RBAC新解)
基於角色的許可權訪問控制RBAC(role-based access control)是以角色為中心進行的訪問控制,也就是判斷主體subject是那個角色的方式進行許可權訪問控制,是粗粒度的
基於資源的許可權訪問控制RBAC(resource-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(); }
結果:
授權流程
- 首先調用
Subject.isPermitted*/hasRole*
介面,其會委托給 SecurityManager,而 SecurityManager 接著會委托給 Authorizer; - Authorizer 是真正的授權者,如果我們調用如 isPermitted(“select”),其首先會通過 PermissionResolver 把字元串轉換成相應的 Permission 實例;
- 在進行授權之前,其會調用相應的 Realm 獲取 Subject 相應的角色/許可權用於匹配傳入的角色/許可權;
- 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'