Shiro - 授權那些事
前面說了認證那些事,這裡繼續說授權那些事。
【1】授權幾個概念
授權,也叫訪問控制,即在應用中控制誰訪問哪些資源(如訪問頁面/編輯資料/頁面操作等)。在授權中需瞭解的幾個關鍵物件:主體(Subject)、資源(Resource)、許可權(Permission)、角色(Role)。
① 主體(Subject)
訪問應用的使用者,在Shiro中使用Subject代表該使用者。使用者只有授權後才允許訪問相應的資源。
② 資源(Resource)
在應用中使用者可以訪問的URL,比如訪問JSP 頁面、檢視/編輯某些資料、訪問某個業務方法、列印文字等等都是資源。使用者只有授權後才能訪問。
③ 許可權(Permission)
安全策略中的原子授權單位,通過許可權我們可以表示在應用中使用者有沒有操作某個資源的權力。即許可權表示在應用中使用者能不能訪問某個資源,如:訪問使用者列表頁面檢視/新增/修改/刪除使用者資料(即很多時候都是CRUD(增查改刪)式許可權控制)等。許可權代表了使用者有沒有操作某個資源的權利,即反映在某個資源上的操作允不允許。
Shiro支援粗粒度許可權(如使用者模組的所有許可權)和細粒度許可權(操作某個使用者的許可權,即例項級別的)
④ 角色(Role)
許可權的集合,一般情況下會賦予使用者角色而不是許可權,即這樣使用者可以擁有一組許可權,賦予許可權時比較方便。典型的如:專案經理、技術總監、CTO、開發工程師等都是角色,不同的角色擁有一組不同的許可權。
【2】授權方式
Shiro支援三種方式的授權:
- 程式設計式:通過寫if/else 授權程式碼塊完成
if(subject.hasRole("admin")){...}else{...}
- 註解式:通過在執行的Java方法上放置相應的註解完成,沒有許可權將丟擲相應的異常
- JSP/GSP 標籤:在JSP/GSP 頁面通過相應的標籤完成。
【3】Permissions
授權的基本單位,通過Permission進行使用者許可權控制。
① 規則
許可權標識語法為:資源識別符號:操作:物件例項ID
。即對哪個資源的哪個例項可以進行什麼操作。其預設支援萬用字元許可權字串,:
,
表示操作的分割,*
表示任意資源/操作/例項。
② 多層次管理
例如:user:query、user:edit。冒號是一個特殊字元,它用來分隔許可權字串的下一部件:第一部分是許可權被操作的領域(印表機),第二部分是被執行的操作。
每個部件能夠保護多個值。因此,除了授予使用者user:query和user:edit許可權外,也可以簡單地授予他們一個:user:query, edit。
還可以用*
號代替所有的值,如:user:*
表示使用者具有所有操作許可權,也可以寫:*:query
表示在所有的領域都有query 的許可權
③ 例項級訪問控制
這種情況通常會使用三個部件:域、操作、被付諸實施的例項
。如:user:edit:manager。
也可以使用萬用字元來定義,如:user:edit:*、user:*:*、user:*:manager
。
如果是部分省略萬用字元,缺少的部件意味著使用者可以訪問所有與之匹配的值,比如:user:edit等價於user:edit:*、user等價於user:*:*
。
注意:萬用字元只能從字串的結尾處省略部件,也就是說user:edit並不等價於user:*:edit
。
【4】授權流程
① 流程圖如下所示:
② 步驟說明如下
- 首先呼叫
Subject.isPermitted*/hasRole*
(或者註解或者JSP標籤)介面,其會委託給SecurityManager,而SecurityManager接著會委託給Authorizer; - Authorizer是真正的授權者,如果呼叫如
isPermitted(“user:view”)
,其首先會通過PermissionResolver把字串轉換成相應的Permission 例項; - 在進行授權之前,其會呼叫相應的Realm(自定義的Realm實現AuthorizingRealm.doGetAuthorizationInfo()方法)獲取Subject 相應的角色/許可權用於匹配傳入的角色/許可權;
- Authorizer 會判斷Realm 的角色/許可權是否和傳入的匹配,如果有多個Realm,會委託給ModularRealmAuthorizer進行迴圈判斷,如果匹配
isPermitted*/hasRole*
會返回true,否則返回false表示授權失敗。
③ ModularRealmAuthorizer
ModularRealmAuthorizer進行多Realm 匹配流程:
- 首先檢查相應的Realm 是否實現了實現了Authorizer;
- 如果實現了Authorizer,那麼接著呼叫其相應的isPermitted*/hasRole* 介面進行匹配;
- 如果有一個Realm匹配那麼將返回true,否則返回false。即,只要有一個匹配,則OK。
④ 自定義CustomRealm完整程式碼
在Shiro認證那些事中我們實現了doGetAuthenticationInfo()用法用於如何認證,這裡實現doGetAuthorizationInfo()方法用於授權。
CustomRealm完整程式碼如下:
public class CustomRealm extends AuthorizingRealm {
@Autowired
ISysUserService sysUserService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(
PrincipalCollection principals) {
SysUser user = (SysUser) principals.getPrimaryPrincipal();
//處理角色
Set<String> roles = new HashSet<String>();
List<SysRole> sysRoles= sysUserService.getRoleListByUserId(user.getId());
for(SysRole r:sysRoles){
roles.add(r.getCode());
}
//處理許可權
List<String> powers = new ArrayList<String>();
List<SysPower> sysPowers= sysUserService.getPowerListByUserId(user.getId());
for(SysPower p:sysPowers){
powers.add(p.getCode());
}
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.addStringPermissions(powers);
simpleAuthorizationInfo.addRoles(roles);
return simpleAuthorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken authenticationToken) throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
//獲取頁面傳來的使用者賬號
String loginName = token.getUsername();
//根據登入賬號從資料庫查詢使用者資訊
SysUser user = sysUserService.getUserByLoginCode(loginName);
System.out.println("從資料庫查詢到的使用者資訊 : "+user);
//一些異常新娘西
if (null == user) {
throw new UnknownAccountException();//沒找到帳號
}
if (user.getStatus()==null||user.getStatus()==0) {
throw new LockedAccountException();//帳號被鎖定
}
//其他異常...
//返回AuthenticationInfo的實現類SimpleAuthenticationInfo
return new SimpleAuthenticationInfo(user, user.getPassword(), this.getName());
//鹽值加密
// ByteSource credentialsSalt = ByteSource.Util.bytes(loginName);
// return new SimpleAuthenticationInfo(user, user.getPassword(), credentialsSalt, this.getName());
}
}
參考博文: