上一節我們講了自定義Realm中的認證(doGetAuthenticationInfo),這節我們繼續講另一個方法doGetAuthorizationInfo授權
授權流程
流程如下:
- 首先呼叫Subject.isPermitted/hasRole介面,其會委託給SecurityManager,而SecurityManager接著會委託給Authorizer;
- Authorizer是真正的授權者,如果我們呼叫如isPermitted(“user:view”),其首先會通過PermissionResolver把字串轉換成相應的Permission例項;
- 在進行授權之前,其會呼叫相應的Realm獲取Subject相應的角色/許可權用於匹配傳入的角色/許可權;
- Authorizer會判斷Realm的角色/許可權是否和傳入的匹配,如果有多個Realm,會委託給ModularRealmAuthorizer進行迴圈判斷,如果匹配如isPermitted*/hasRole*會返回true,否則返回false表示授權失敗。
ModularRealmAuthorizer進行多Realm匹配流程:
- 首先檢查相應的Realm是否實現了實現了Authorizer;
- 如果實現了Authorizer,那麼接著呼叫其相應的isPermitted*/hasRole*介面進行匹配;
- 如果有一個Realm匹配那麼將返回true,否則返回false。
如果Realm進行授權的話,應該繼承AuthorizingRealm,其流程是:
1.1、如果呼叫hasRole,則直接獲取AuthorizationInfo.getRoles()與傳入的角色比較即可;
1.2、首先如果呼叫如isPermitted(“user:view”),首先通過PermissionResolver將許可權字串轉換成相應的Permission例項,預設使用WildcardPermissionResolver,即轉換為萬用字元的WildcardPermission;
2、通過AuthorizationInfo.getObjectPermissions()得到Permission例項集合;通過AuthorizationInfo.
getStringPermissions()得到字串集合並通過PermissionResolver解析為Permission例項;然後獲取使用者的角色,並通過RolePermissionResolver解析角色對應的許可權集合(預設沒有實現,可以自己提供);
3、接著呼叫Permission. implies(Permission p)逐個與傳入的許可權比較,如果有匹配的則返回true,否則false。
先看一段簡單的授權方法重寫
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//獲取使用者名稱
String username = (String) principals.getPrimaryPrincipal();
//此處從資料庫獲取該使用者的角色
Set<String> roles = getRolesByUserName(username);
//此處從資料庫獲取該角色的許可權
Set<String> permissions = getPermissionsByUserName(username);
//放到info裡返回
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.setStringPermissions(permissions);
info.setRoles(roles);
return info;
}
複製程式碼
PrincipalCollection
因為我們可以在Shiro中同時配置多個Realm,所以呢身份資訊可能就有多個;因此其提供了PrincipalCollection用於聚合這些身份資訊:
public interface PrincipalCollection extends Iterable, Serializable {
Object getPrimaryPrincipal(); //得到主要的身份
<T> T oneByType(Class<T> type); //根據身份型別獲取第一個
<T> Collection<T> byType(Class<T> type); //根據身份型別獲取一組
List asList(); //轉換為List
Set asSet(); //轉換為Set
Collection fromRealm(String realmName); //根據Realm名字獲取
Set<String> getRealmNames(); //獲取所有身份驗證通過的Realm名字
boolean isEmpty(); //判斷是否為空
}
複製程式碼
因為PrincipalCollection聚合了多個,此處最需要注意的是getPrimaryPrincipal,如果只有一個Principal那麼直接返回即可,如果有多個Principal,則返回第一個(因為內部使用Map儲存,所以可以認為是返回任意一個);oneByType / byType根據憑據的型別返回相應的Principal;fromRealm根據Realm名字(每個Principal都與一個Realm關聯)獲取相應的Principal。
AuthorizationInfo
AuthorizationInfo用於聚合授權資訊的:
public interface AuthorizationInfo extends Serializable {
Collection<String> getRoles(); //獲取角色字串資訊
Collection<String> getStringPermissions(); //獲取許可權字串資訊
Collection<Permission> getObjectPermissions(); //獲取Permission物件資訊
}
複製程式碼
當我們使用AuthorizingRealm時,如果身份驗證成功,在進行授權時就通過doGetAuthorizationInfo方法獲取角色/許可權資訊用於授權驗證。 Shiro提供了一個實現SimpleAuthorizationInfo,大多數時候使用這個即可。
我們再跟蹤一下程式碼,看看是如何呼叫Authorizer的
subject.hasRole("admin")
複製程式碼
- 呼叫DelegatingSubject類的hasRole方法
public boolean hasRole(String roleIdentifier) {
return hasPrincipals() && securityManager.hasRole(getPrincipals(), roleIdentifier);
}
複製程式碼
- 呼叫AuthorizingSecurityManager的hasRole
public boolean hasRole(PrincipalCollection principals, String roleIdentifier) {
return this.authorizer.hasRole(principals, roleIdentifier);
}
複製程式碼
- AuthorizingSecurityManager類在建立的時候就注入了ModularRealmAuthorizer類為authorizer
public AuthorizingSecurityManager() {
super();
this.authorizer = new ModularRealmAuthorizer();
}
複製程式碼
- 繼續跟進到ModularRealmAuthorizer的hasRole方法
public boolean hasRole(PrincipalCollection principals, String roleIdentifier) {
assertRealmsConfigured();
for (Realm realm : getRealms()) {
if (!(realm instanceof Authorizer)) continue;
if (((Authorizer) realm).hasRole(principals, roleIdentifier)) {
return true;
}
}
return false;
}
複製程式碼
- 此處的hasRole是呼叫AuthorizingRealm抽象類的hasRole方法。同理,isPermitted也是最後呼叫到此。
public boolean hasRole(PrincipalCollection principal, String roleIdentifier) {
AuthorizationInfo info = getAuthorizationInfo(principal);
return hasRole(roleIdentifier, info);
}
protected boolean hasRole(String roleIdentifier, AuthorizationInfo info) {
return info != null && info.getRoles() != null && info.getRoles().contains(roleIdentifier);
}
public boolean isPermitted(PrincipalCollection principals, String permission) {
Permission p = getPermissionResolver().resolvePermission(permission);
return isPermitted(principals, p);
}
public boolean isPermitted(PrincipalCollection principals, Permission permission) {
AuthorizationInfo info = getAuthorizationInfo(principals);
return isPermitted(permission, info);
}
//changed visibility from private to protected for SHIRO-332
protected boolean isPermitted(Permission permission, AuthorizationInfo info) {
Collection<Permission> perms = getPermissions(info);
if (perms != null && !perms.isEmpty()) {
for (Permission perm : perms) {
if (perm.implies(permission)) {
return true;
}
}
}
return false;
}
複製程式碼