權(quán)限控制可以描述為:“誰”對(duì)“什么”進(jìn)行什么”操作"。通過權(quán)限控制,我們可以有效控制用戶的行為操作,進(jìn)而做到對(duì)資源的有效保護(hù)。
一、SHIRO授權(quán)示例
shiro支持兩種兩種訪問控制方式,分別為:基于角色的訪問控制、基于資源的訪問控制。
基于角色的訪問控制:
Subject subject = SecurityUtils.getSubject();
subject.hasRole("tole1");
subject.hasAllRoles("role1","role2");
基于資源的訪問控制:
Subject subject = SecurityUtils.getSubject();
subject.isPermitted("user:create");
subject.isPermittedAll("user:update","user:delete");
下面本文將對(duì)shiro的內(nèi)部授權(quán)邏輯過程進(jìn)行詳細(xì)分析。
二、SHIRO授權(quán)過程
“基于角色的訪問控制邏輯”和“基于資源的訪問控制邏輯”內(nèi)部過程基本是一樣的,因此本文分析只分析下“基于資源的訪問控制邏輯”的過程。
使用者調(diào)用Subject.isPermitted方法進(jìn)行授權(quán),isPermitted接口實(shí)現(xiàn)是在DelegatingSubject中,代碼如下:
public class DelegatingSubject implements Subject {
public boolean isPermitted(String permission) {
return hasPrincipals() && securityManager.isPermitted(getPrincipals(), permission);
}
public boolean isPermitted(Permission permission) {
return hasPrincipals() && securityManager.isPermitted(getPrincipals(), permission);
}
public boolean[] isPermitted(String... permissions) {
if (hasPrincipals()) {
return securityManager.isPermitted(getPrincipals(), permissions);
} else {
return new boolean[permissions.length];
}
}
...
}
從代碼可以發(fā)現(xiàn),DelegatingSubject又委托給了SecurityManager,調(diào)用SecurityManager中的isPermitted方法。下面看看SecurityManager的授權(quán)處理。
首先看看shiro核心邏輯類圖。

SecurityManager其實(shí)是一個(gè)代理,SecurityManager代理了Authorizer,SecurityManager子類AuthorizingSecuritymanager為具體代理子類,看看類中相應(yīng)方法定義:
public boolean isPermitted(PrincipalCollection principals, String permissionString) {
return this.authorizer.isPermitted(principals, permissionString);
}
public boolean isPermitted(PrincipalCollection principals, Permission permission) {
return this.authorizer.isPermitted(principals, permission);
}
public boolean[] isPermitted(PrincipalCollection principals, String... permissions) {
return this.authorizer.isPermitted(principals, permissions);
}
...
確實(shí)是代理模式,類中真正校驗(yàn)是Authorizer做的,具體Authorizer為ModularRealmAuthorizer,ModularRealmAuthorizer中方法定義如下:
public boolean isPermitted(PrincipalCollection principals, String permission) {
assertRealmsConfigured();
for (Realm realm : getRealms()) {
if (!(realm instanceof Authorizer)) continue;
if (((Authorizer) realm).isPermitted(principals, permission)) {
return true;
}
}
return false;
}
public boolean isPermitted(PrincipalCollection principals, Permission permission) {
assertRealmsConfigured();
for (Realm realm : getRealms()) {
if (!(realm instanceof Authorizer)) continue;
if (((Authorizer) realm).isPermitted(principals, permission)) {
return true;
}
}
return false;
}
從上面代碼又可以看出,ModularRealmAuthorizer又把校驗(yàn)的工作交給了Realm,授權(quán)的Realm為AuthorizingRealm,AuthorizingRealm中實(shí)現(xiàn)了isPermitted方法,方法定義如下:
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);
}
如果是字符串Permission,首先要進(jìn)行處理,處理過程如下:
PermissionResolver對(duì)字符串權(quán)限進(jìn)行轉(zhuǎn)化處理,其繼承類為WildcardPermissionResolver.
List<String> parts = CollectionUtils.asList(wildcardString.split(PART_DIVIDER_TOKEN));
上面這句代碼首先將權(quán)限按 " : "進(jìn)行分割成數(shù)組。然后對(duì)這個(gè)數(shù)組中的每個(gè)元素再按照 ”,“ 分割。
Set<String> subparts = CollectionUtils.asSet(part.split(SUBPART_DIVIDER_TOKEN));
最終返回形式為List<Set<String>>。
那么轉(zhuǎn)化后怎么用了,看看WildcardPermission的繼承關(guān)系:
public class WildcardPermission implements Permission, Serializable{
......
}
而Permission的定義如下:
public interface Permission {
boolean implies(Permission p);
}
WildcardPermission的implices方法實(shí)現(xiàn)如下:
public boolean implies(Permission p) {
if (!(p instanceof WildcardPermission)) {
return false;
}
WildcardPermission wp = (WildcardPermission) p;
List<Set<String>> otherParts = wp.getParts();
int i = 0;
for (Set<String> otherPart : otherParts) {
if (getParts().size() - 1 < i) {
return true;
} else {
Set<String> part = getParts().get(i);
if (!part.contains(WILDCARD_TOKEN) && !part.containsAll(otherPart)) {
return false;
}
i++;
}
}
// If this permission has more parts than the other parts, only imply it if all of the other parts are wildcards
for (; i < getParts().size(); i++) {
Set<String> part = getParts().get(i);
if (!part.contains(WILDCARD_TOKEN)) {
return false;
}
}
return true;
}
這個(gè)代碼的意思就是instance的權(quán)限是否包含目標(biāo)權(quán)限,如果包含,那么就鑒權(quán)成功,返回True,否則鑒權(quán)不通過,這樣就完成了權(quán)限校驗(yàn)。
我們需要自定義授權(quán)邏輯時(shí),繼承AuthorizingRealm,然后自定義實(shí)現(xiàn)其授權(quán)方法即可!
三、總結(jié)
總結(jié)一下,權(quán)限校驗(yàn)流程如下:

具體流程為:
1)首先調(diào)用Subject.isPermitted/hasRole接口,其會(huì)委托給SecurityManager,而SecurityManager接著會(huì)委托給Authorizer(SecurityManager繼承了Authorizer接口,其真正業(yè)務(wù)邏輯還是委托給相應(yīng)的Authorizer實(shí)例。ModularRealmAuthenticator是Authenticator的一個(gè)實(shí)現(xiàn)類,一般主要用這個(gè)實(shí)現(xiàn)類)。
2)Authorizer是真正的授權(quán)者,如果我們調(diào)用如isPermitted(“user:view”),其首先會(huì)通過PermissionResolver把字符串轉(zhuǎn)換成相應(yīng)的Permission實(shí)例;
3)在進(jìn)行授權(quán)之前,其會(huì)調(diào)用相應(yīng)的Realm獲取Subject相應(yīng)的角色/權(quán)限用于匹配傳入的角色/權(quán)限;
4)Authorizer會(huì)判斷Realm的角色/權(quán)限是否和傳入的匹配,如果有多個(gè)Realm,會(huì)委托給ModularRealmAuthorizer進(jìn)行循環(huán)判斷,如果匹配如isPermitted/hasRole會(huì)返回true,否則返回false表示授權(quán)失敗。
一般是使用Realm進(jìn)行授權(quán),此時(shí)繼承AuthorizingRealm,其流程是:
1.如果調(diào)用hasRole,則直接獲取AuthorizationInfo.getRoles()與傳入的角色比較即可;
2、如果調(diào)用isPermitted(“user:view”),首先通過PermissionResolver將權(quán)限字符串轉(zhuǎn)換成相應(yīng)的Permission實(shí)例,默認(rèn)使用WildcardPermissionResolver,即轉(zhuǎn)換為通配符的WildcardPermission;
3、通過AuthorizationInfo.getObjectPermissions()得到Permission實(shí)例集合;通過AuthorizationInfo. getStringPermissions()得到字符串集合并通過PermissionResolver解析為Permission實(shí)例;然后獲取用戶的角色,并通過RolePermissionResolver解析角色對(duì)應(yīng)的權(quán)限集合(默認(rèn)沒有實(shí)現(xiàn),可以自己提供);
4、接著調(diào)用Permission. implies(Permission p)逐個(gè)與傳入的權(quán)限比較,如果有匹配的則返回true,否則false。
ModularRealmAuthorizer進(jìn)行多Realm匹配流程:
1)首先檢查相應(yīng)的Realm是否實(shí)現(xiàn)了實(shí)現(xiàn)了Authorizer;
2)如果實(shí)現(xiàn)了Authorizer,那么接著調(diào)用其相應(yīng)的isPermitted/hasRole接口進(jìn)行匹配;
3)如果有一個(gè)Realm匹配那么將返回true,否則返回false。
SHIRO授權(quán)分析到此為止,祝工作順利,天天開心!