受權,也叫訪問控制,即在應用中控制誰能訪問哪些資源(如訪問頁面/編輯數據/頁面操做等)。在受權中需瞭解的幾個關鍵對象:主體(Subject)、資源(Resource)、權限(Permission)、角色(Role)。數據庫
主體
主體,即訪問應用的用戶,在 Shiro 中使用 Subject 表明該用戶。用戶只有受權後才容許訪問相應的資源。編程
資源
在應用中用戶能夠訪問的任何東西,好比訪問 JSP 頁面、查看/編輯某些數據、訪問某個業務方法、打印文本等等都是資源。用戶只要受權後才能訪問。安全
權限
安全策略中的原子受權單位,經過權限咱們能夠表示在應用中用戶有沒有操做某個資源的權力。即權限表示在應用中用戶能不能訪問某個資源,如: 訪問用戶列表頁面
查看/新增/修改/刪除用戶數據(即不少時候都是 CRUD(增查改刪)式權限控制)
打印文檔等等。。。app
如上能夠看出,權限表明了用戶有沒有操做某個資源的權利,即反映在某個資源上的操做允不容許,不反映誰去執行這個操做。因此後續還須要把權限賦予給用戶,即定義哪一個用戶容許在某個資源上作什麼操做(權限),Shiro 不會去作這件事情,而是由實現人員提供。ide
Shiro 支持粗粒度權限(如用戶模塊的全部權限)和細粒度權限(操做某個用戶的權限,即實例級別的),後續部分介紹。post
角色
角色表明了操做集合,能夠理解爲權限的集合,通常狀況下咱們會賦予用戶角色而不是權限,即這樣用戶能夠擁有一組權限,賦予權限時比較方便。典型的如:項目經理、技術總監、CTO、開發工程師等都是角色,不一樣的角色擁有一組不一樣的權限。測試
隱式角色:
即直接經過角色來驗證用戶有沒有操做權限,如在應用中 CTO、技術總監、開發工程師可使用打印機,假設某天不容許開發工程師使用打印機,此時須要從應用中刪除相應代碼;再如在應用中 CTO、技術總監能夠查看用戶、查看權限;忽然有一天不容許技術總監查看用戶、查看權限了,須要在相關代碼中把技術總監角色從判斷邏輯中刪除掉;即粒度是以角色爲單位進行訪問控制的,粒度較粗;若是進行修改可能形成多處代碼修改。ui
顯示角色:
在程序中經過權限控制誰能訪問某個資源,角色聚合一組權限集合;這樣假設哪一個角色不能訪問某個資源,只須要從角色表明的權限集合中移除便可;無須修改多處代碼;即粒度是以資源/實例爲單位的;粒度較細。spa
基於角色的權限訪問控制RBAC(role-based access control)是以角色爲中心進行的訪問控制,也就是判斷主體subject是那個角色的方式進行權限訪問控制,是粗粒度的code
基於資源的權限訪問控制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;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'