文档章节

Shiro源码分析之鉴权方式

Lucare
 Lucare
发布于 2018/07/22 00:34
字数 1390
阅读 184
收藏 0

一、 怎么用

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类关系图: image

在不同层级的构造器中分别设置了是否启用权限缓存和身份验证缓存

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的开发人员设置为平台管理员,可以坐拥天下,在功能不断完善的情况下不需要再补充权限。

© 著作权归作者所有

Lucare

Lucare

粉丝 6
博文 11
码字总数 11404
作品 0
深圳
程序员
私信 提问
使用shiro保护你的springboot应用

springboot中使用shiro大都是通过shiro-spring.jar进行的整合的,虽然不是太复杂,但是也无法做到spring-boot-starter风格的开箱即用。项目中经常用到的功能比如:验证码、密码错误次数限制、...

wangjie2016
2018/01/05
0
1
Shiro用starter方式优雅整合到SpringBoot中

Shiro用starter方式优雅整合到SpringBoot中 网上找到大部分文章都是以前SpringMVC下的整合方式,很多人都不知道shiro提供了官方的starter可以方便地跟SpringBoot整合。本文介绍我的3种整合思...

煲煲菜
09/29
0
0
JFianl整合Shiro

原文: 入门必看 英文好的可以直接看官网教程,英文不好的可以看下开涛的博客《跟我学Shiro》系列 在看教程之前,最好了解想一些shiro的概念:Apache Shiro Terminology 自己入门时踩的坑 一...

听_风
2016/08/30
140
0
SpringBoot 优雅的整合 Shiro

Apache Shiro是一个功能强大且易于使用的Java安全框架,可执行身份验证,授权,加密和会话管理。借助Shiro易于理解的API,您可以快速轻松地保护任何应用程序 - 从最小的移动应用程序到最大的...

木云凌
03/19
436
0
Guns v4.1 发布,做简洁的管理系统

Guns 4.1更新说明: guns-admin提供rest api服务,并以jwt方式鉴权,所有以开头的接口走这种鉴权方式,其他接口仍为shiro鉴权 Guns简介: Guns 基于 SpringBoot,致力于做更简洁的后台管理系...

stylefeng
2018/07/24
4K
4

没有更多内容

加载失败,请刷新页面

加载更多

【TencentOS tiny】深度源码分析(4)——消息队列

消息队列 在前一篇文章中【TencentOS tiny学习】源码分析(3)——队列 我们描述了TencentOS tiny的队列实现,同时也点出了TencentOS tiny的队列是依赖于消息队列的,那么我们今天来看看消息...

杰杰1号
4分钟前
1
0
Hive

这就是那个 JAVA 类 package cn.itcast.bigdata;import java.util.HashMap;import org.apache.hadoop.hive.ql.exec.UDF;public class PhoneNbrToArea extends UDF{privat......

Garphy
4分钟前
2
0
Springboot开发,第二天

SpringBoot学习,第二天 目录:1、Springboot整合Listener 2、Springboot访问静态资源 3、异常处理 4、热部署 一、SpringBoot整合Listener 两种方式完成组件的注册 1、通过注解扫描完成组件的...

有一个小阿飞
8分钟前
3
0
BeginnersBook Perl 教程

来源:ApacheCN BeginnersBook 翻译项目 译者:飞龙 协议:CC BY-NC-SA 4.0 贡献指南 本项目需要校对,欢迎大家提交 Pull Request。 请您勇敢地去翻译和改进翻译。虽然我们追求卓越,但我们并...

ApacheCN_飞龙
20分钟前
2
0
我的Java秋招面经大合集

阿里面经 阿里中间件研发面经 蚂蚁金服研发面经 岗位是研发工程师,直接找蚂蚁金服的大佬进行内推。 我参与了阿里巴巴中间件部门的提前批面试,一共经历了四次面试,拿到了口头offer。 然后我...

Java技术江湖
25分钟前
6
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部