Shiro的鑑權方式
一、 怎麼用
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>
二、啥原理
三種方式背後的原理是一樣的,由簡單到複雜的順序來看看。
1、標籤式
shiro提供了RoleTag和PermissionTag的抽象類,還有兩個簡單的實現:
public class HasRoleTag extends RoleTag {
//TODO - complete JavaDoc
public HasRoleTag() {
}
protected boolean showTagBody(String roleName) {
return getSubject() != null && getSubject().hasRole(roleName);
}
}
public class HasPermissionTag extends PermissionTag {
//TODO - complete JavaDoc
public HasPermissionTag() {
}
protected boolean showTagBody(String p) {
return isPermitted(p);
}
}
分別呼叫的是DelegatingSubject的hasRole方法和isPermitted方法:
public boolean hasRole(String roleIdentifier) {
return hasPrincipals() && securityManager.hasRole(getPrincipals(), roleIdentifier);
}
public boolean isPermitted(String permission) {
return hasPrincipals() && securityManager.isPermitted(getPrincipals(), permission);
}
然後又呼叫AuthorizingSecurityManager的相關方法,AuthorizingSecurityManager持有一個ModularRealmAuthorizer型別的Authorizer:
this.authorizer = new ModularRealmAuthorizer();
相關方法又轉移至呼叫ModularRealmAuthorizer:
ModularRealmAuthorizer 進行多 Realm 匹配流程:
1、首先檢查相應的 Realm 是否實現了實現了 Authorizer;
2、如果實現了 Authorizer,那麼接著呼叫其相應的 isPermitted*/hasRole*介面進行匹配;
3、如果有一個 Realm 匹配那麼將返回 true,否則返回 false。
2、註解式
@RequiresRoles("admin")
@RequiresPermissions("admin:role:view")
@RequestMapping(value = "/configIndex", method = { RequestMethod.GET })
public String index(Model model) {
return "rolemgr/roleConfig/configIndex";
}
技術細節
基於攔截器實現(AuthorizingAnnotationMethodInterceptor)
動態代理技術(CglibAopProxy)
spring InvocableHandlerMethod#invoke
CglibAopProxy.DynamicAdvisedInterceptor#intercept
AnnotationMethodInterceptor
AuthorizingAnnotationMethodInterceptor#assertAuthorized
RoleAnnotationHandler#assertAuthorized
委託呼叫
DelegatingSubject:
public void checkRole(String role) throws AuthorizationException {
assertAuthzCheckPossible();
securityManager.checkRole(getPrincipals(), role);
}
AuthorizingSecurityManager:
public void checkRole(PrincipalCollection principals, String role) throws AuthorizationException {
this.authorizer.checkRole(principals, role);
}
ModularRealmAuthorizer:
public void checkRole(PrincipalCollection principals, String role) throws AuthorizationException {
assertRealmsConfigured();
if (!hasRole(principals, role)) {
throw new UnauthorizedException("Subject does not have role [" + role + "]");
}
}
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;
}
AuthorizingRealm:
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);
}
關於 getAuthorizationInfo 方法:
protected AuthorizationInfo getAuthorizationInfo(PrincipalCollection principals) {
if (principals == null) {
return null;
}
AuthorizationInfo info = null;
Cache<Object, AuthorizationInfo> cache = getAvailableAuthorizationCache();
if (cache != null) {
Object key = getAuthorizationCacheKey(principals);
info = cache.get(key);
}
if (info == null) {
// 如果cache中沒有找到info則呼叫模板方法
info = doGetAuthorizationInfo(principals);
// info不為null並且cache已經建立了,則快取info資訊
if (info != null && cache != null) {
Object key = getAuthorizationCacheKey(principals);
cache.put(key, info);
}
}
return info;
}
- 首先還是找可用的許可權快取:
private Cache<Object, AuthorizationInfo> getAvailableAuthorizationCache() {
Cache<Object, AuthorizationInfo> cache = getAuthorizationCache();
if (cache == null && isAuthorizationCachingEnabled()) {
cache = getAuthorizationCacheLazy();
}
return cache;
}
回顧下安全管理器的結構,RealmSecurityManager繼承了CachingSecurityManager,當CachingSecurityManager設定了cacheManager,會呼叫用子類的afterCacheManagerSet方法:
public void setCacheManager(CacheManager cacheManager) {
this.cacheManager = cacheManager;
afterCacheManagerSet();
}
RealmSecurityManager將會為每個Realm設定cacheManager:
protected void afterCacheManagerSet() {
applyCacheManagerToRealms();
}
protected void applyCacheManagerToRealms() {
CacheManager cacheManager = getCacheManager();
Collection<Realm> realms = getRealms();
if (cacheManager != null && realms != null && !realms.isEmpty()) {
for (Realm realm : realms) {
if (realm instanceof CacheManagerAware) {
((CacheManagerAware) realm).setCacheManager(cacheManager);
}
}
}
}
看下AuthorizingRealm類關係圖:
在不同層級的構造器中分別設定了是否啟用許可權快取和身份驗證快取
this.authorizationCachingEnabled = true;
this.authenticationCachingEnabled = false;
再看CachingRealm,跟安全管理器的做法類似,並且為每個realm設定cacheManager的時候就已經觸發了子類的相關操作:
public void setCacheManager(CacheManager cacheManager) {
this.cacheManager = cacheManager;
afterCacheManagerSet();
}
AuthorizingRealm的實現就是通過cacheManager去獲取許可權相關的cache:
protected void afterCacheManagerSet() {
super.afterCacheManagerSet();
//trigger obtaining the authorization cache if possible
getAvailableAuthorizationCache();
}
如果我們沒有明確配置cacheManager(作為securityManager的屬性注入),那麼此時是獲取不到的,cache為null並且啟用了許可權快取,現在就要臨時構造一個:
cache = getAuthorizationCacheLazy();
- 獲取AuthorizationInfo
以principals作為key取AuthorizationInfo:
info = cache.get(key);
if (info == null) {
info = doGetAuthorizationInfo(principals);
if (info != null && cache != null) {
Object key = getAuthorizationCacheKey(principals);
cache.put(key, info);
}
}
這個時候就轉到我們自己的實現了,我們自己去獲取許可權,然後返回一個AuthorizationInfo,就是許可權相關的資訊。
3、總結
程式設計式很暴力也很直接,直接操作subject的相關方法來鑑權,其他兩種方式拐彎抹角地也是操作的subject,然後再委託給securityManager。
具體是AuthorizingSecurityManager層實現的,它是直接new了一個ModularRealmAuthorizer,相關操作又轉交給它,它又梳理一下,交給我們實現的realm(父類AuthorizingRealm層實現)。
ModularRealmAuthorizer是怎麼獲取到realm的?
也是我們給securityManager配置的,類似上面cacheManager的set方法,安全管理器用了很多這樣的方法,給它本身注入相關屬性時,就把相關聯的set了。
AuthorizingSecurityManager這樣實現的:
protected void afterRealmsSet() {
super.afterRealmsSet();
if (this.authorizer instanceof ModularRealmAuthorizer) {
((ModularRealmAuthorizer) this.authorizer).setRealms(getRealms());
}
}
從類的呼叫關係來看:
DelegatingSubject -> AuthorizingSecurityManager -> ModularRealmAuthorizer -> Realm
- 註解呼叫棧
RequiresRoles :checkRole -> checkRole -> checkRole -> hasRole
RequiresPermissions:checkPermission -> checkPermission -> checkPermission -> isPermitted
三、學到什
- Shiro作為框架是如何承上啟下的,上接servlet規範,中搭spring順風車,下給開發者自己實現。
- 實現的技巧
- 開發中有效的配置,哪些是必要的,哪些是預設的。
- 如何提升開發效率
例如在自己的XxRealm中:
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
if (MySubjectUtils.isPatformAdmin()) {
simpleAuthorizationInfo.addStringPermission("*");
} else {
//查詢關聯的許可權adminUser:create:01001001
}
}
或者在自己的許可權標籤中:
public class HasAnyPermissionTag extends PermissionTag {
@Override
protected boolean showTagBody(String permissions) {
boolean hasPermission = false;
Subject subject = getSubject();
if(MySubjectUtils.isPatformAdmin()){
return true;
}
//......
}
}
這樣作為開發者對於角色許可權的配置可以省掉了,將特定id或者name的開發人員設定為平臺管理員,可以坐擁天下,在功能不斷完善的情況下不需要再補充許可權。