文档章节

Shiro源码分析(5) - 授权器(Authorizer)

xiaoqiyiye
 xiaoqiyiye
发布于 2018/02/05 18:06
字数 2037
阅读 302
收藏 1
本文在于分析Shiro源码,对于新学习的朋友可以参考
[开涛博客](http://jinnianshilongnian.iteye.com/blog/2018398)进行学习。

在Shiro中Authorizer接口用来处理用户的角色和权限控制访问操作,其接口代码如下。

/**
 * 判断是否有指定的权限
 */
boolean isPermitted(PrincipalCollection principals, String permission);

/**
 * 判断是否有指定的权限
 */
boolean isPermitted(PrincipalCollection subjectPrincipal, Permission permission);

/**
 * 判断是否有指定的权限集合
 */
boolean[] isPermitted(PrincipalCollection subjectPrincipal, String... permissions);

/**
 * 判断是否有指定的所有权限集合
 */
boolean isPermittedAll(PrincipalCollection subjectPrincipal, String... permissions);

/**
 * 判断是否有指定的所有权限集合
 */
boolean isPermittedAll(PrincipalCollection subjectPrincipal, Collection<Permission> permissions);

/**
 * 检测是否存在权限,否则抛异常
 */
void checkPermission(PrincipalCollection subjectPrincipal, String permission) throws AuthorizationException;

/**
 * 检测是否存在权限,否则抛异常
 */
void checkPermission(PrincipalCollection subjectPrincipal, Permission permission) throws AuthorizationException;

/**
 * 检测是否存在权限,否则抛异常
 */
void checkPermissions(PrincipalCollection subjectPrincipal, String... permissions) throws AuthorizationException;

/**
 * 检测是否存在权限,否则抛异常
 */
void checkPermissions(PrincipalCollection subjectPrincipal, Collection<Permission> permissions) throws AuthorizationException;

/**
 * 判断是否有指定的角色
 */
boolean hasRole(PrincipalCollection subjectPrincipal, String roleIdentifier);

/**
 * 判断是否有指定的角色集合
 */
boolean[] hasRoles(PrincipalCollection subjectPrincipal, List<String> roleIdentifiers);

/**
 * 判断是否有指定的所有角色集合
 */
boolean hasAllRoles(PrincipalCollection subjectPrincipal, Collection<String> roleIdentifiers);

/**
 * 检测角色
 */
void checkRole(PrincipalCollection subjectPrincipal, String roleIdentifier) throws AuthorizationException;

/**
 * 检测角色
 */
void checkRoles(PrincipalCollection subjectPrincipal, Collection<String> roleIdentifiers) throws AuthorizationException;

/**
 * 检测角色
 */
void checkRoles(PrincipalCollection subjectPrincipal, String... roleIdentifiers) throws AuthorizationException;

Authorizer接口中主要提供了判断和检测角色权限的方法。AuthorizingRealm是Authorizer的实现子类,这个类同时实现了Authorizer、PermissionResolverAware、RolePermissionResolverAware接口,另外还继承了AuthenticatingRealm抽象类,关于Realm的分析可以参看上一篇内容。

下面我们正式开始分析AuthorizingRealm类,我们还是从属性和构造方法分析。请看下面代码。

//是否使用缓存
private boolean authorizationCachingEnabled;
//缓存被认证过的信息
private Cache<Object, AuthorizationInfo> authorizationCache;
//授权缓存名称
private String authorizationCacheName;
//权限解析器
private PermissionResolver permissionResolver;
//角色解析器
private RolePermissionResolver permissionRoleResolver;

public AuthorizingRealm(CacheManager cacheManager, CredentialsMatcher matcher) {
	super();
	if (cacheManager != null) setCacheManager(cacheManager);
	if (matcher != null) setCredentialsMatcher(matcher);
    // 默认开启缓存
	this.authorizationCachingEnabled = true;
    //通配符权限解析器
	this.permissionResolver = new WildcardPermissionResolver();
    //设置缓存名称
	int instanceNumber = INSTANCE_COUNT.getAndIncrement();
	this.authorizationCacheName = getClass().getName() + DEFAULT_AUTHORIZATION_CACHE_SUFFIX;
	if (instanceNumber > 0) {
		this.authorizationCacheName = this.authorizationCacheName + "." + instanceNumber;
	}
}

因为AuthorizingRealm实现了初始化接口Initializable,所以在AuthorizingRealm创建时会先调用onInit()方法进行初始化操作。初始化主要获取缓存对象Cache。

    private Cache<Object, AuthorizationInfo> getAuthorizationCacheLazy() {
        // 首先authorizationCache会为空
        if (this.authorizationCache == null) {

            if (log.isDebugEnabled()) {
                log.debug("No authorizationCache instance set.  Checking for a cacheManager...");
            }

            CacheManager cacheManager = getCacheManager();

            if (cacheManager != null) {
                // 根据缓存名称获取缓存对象
                String cacheName = getAuthorizationCacheName();
                this.authorizationCache = cacheManager.getCache(cacheName);
            } else {
                if (log.isDebugEnabled()) {
                    log.debug("No cache or cacheManager properties have been set.  Authorization cache cannot " +
                            "be obtained.");
                }
            }
        }

        return this.authorizationCache;
    }

在Authorizer接口中方法都很类似,我们以分别选出角色和权限的方法进行分析,其他方法一样。我们即将对下面两个方法进行分析。

/**
 * 判断是否有指定的权限
 */
boolean isPermitted(PrincipalCollection principals, String permission);

/**
 * 判断是否有指定的角色
 */
boolean hasRole(PrincipalCollection subjectPrincipal, String roleIdentifier);

isPermitted方法分析

下面先给出关于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);
}

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;
}

在上面的代码中,首先通过getPermissionResolver().resolvePermission(permission)将指定字符串的权限(可以理解为权限码或权限名称)封装成Permission权限对象,这里使用的是WildcardPermission实现类。接下来,调用getAuthorizationInfo(principals)方法通过凭证获取认证信息。最后就是调用Permission接口提供的implies(permission)方法来判断是否真实包含权限。

我们来分析下getAuthorizationInfo(principals)方法。

protected AuthorizationInfo getAuthorizationInfo(PrincipalCollection principals) {

	if (principals == null) {
		return null;
	}

	AuthorizationInfo info = null;

	if (log.isTraceEnabled()) {
		log.trace("Retrieving AuthorizationInfo for principals [" + principals + "]");
	}
        
    // 获取缓存对象,如果没有开启缓存功能,则返回null
    // 这里会调用我们在前面分析过的getAuthorizationCacheLazy()方法
	Cache<Object, AuthorizationInfo> cache = getAvailableAuthorizationCache();
	if (cache != null) {
		if (log.isTraceEnabled()) {
			log.trace("Attempting to retrieve the AuthorizationInfo from cache.");
		}
        // 获取凭证使用授权时的缓存Key,这个方法直接返回principals本身
        // 在业务处理过程中可以对该方法进行重写
		Object key = getAuthorizationCacheKey(principals);
        //从缓存中获取AuthorizationInfo认证信息
		info = cache.get(key);
		if (log.isTraceEnabled()) {
			if (info == null) {
				log.trace("No AuthorizationInfo found in cache for principals [" + principals + "]");
			} else {
				log.trace("AuthorizationInfo found in cache for principals [" + principals + "]");
			}
		}
	}

        // 如果没有开启缓存或缓存中不存在,则需要重doGetAuthorizationInfo(principals)方法中获取
        // 这个方法是抽象方法由子类实现如何获取
	if (info == null) 
		info = doGetAuthorizationInfo(principals);
		if (info != null && cache != null) {
			if (log.isTraceEnabled()) {
				log.trace("Caching authorization info for principals: [" + principals + "].");
			}
           //如果获取成功,则存放到缓存中去
			Object key = getAuthorizationCacheKey(principals);
			cache.put(key, info);
		}
	}

	return info;
}

protected Object getAuthorizationCacheKey(PrincipalCollection principals) {
	return principals;
}

hasRole方法分析

hasRole方法很简单,获取AuthorizationInfo对象后,直接判断是否包含给定的角色信息。

源代码如下:

public boolean hasRole(PrincipalCollection principal, String roleIdentifier) {
	AuthorizationInfo info = getAuthorizationInfo(principal);
	return hasRole(roleIdentifier, info);
}

protected boolean hasRole(String roleIdentifier, AuthorizationInfo info) {
    // 判断AuthorizationInfo中是否存在角色信息
	return info != null && info.getRoles() != null && info.getRoles().contains(roleIdentifier);
}

从上面分析isPermitted和hasRole这两个方法时我们会想,角色和权限信息从哪里获取来并存放在AuthorizationInfo对象中去的,很显然是抽象方法doGetAuthorizationInfo(PrincipalCollection principals)。

在使用Shiro进行认证时,肯定需要根据业务定义自己的Realm数据域来源,而AuthorizingRealm实现授权器(Authorizer)的同时还继承了AuthenticatingRealm,也就是说如果我们需要进行授权操作,自定义的Realm需要从AuthorizingRealm继承,从而根据业务实现doGetAuthorizationInfo(PrincipalCollection principals)方法。

关于授权器(Authorizer)的分析基本上就这么多,下面我们随便把上面说过的Permission权限对象分析一下。在上面我们提及过使用了WildcardPermission实现类,这是一种通配符匹配的实现方式。

WildcardPermission分析

我们回到上面的代码片段:

Permission p = getPermissionResolver().resolvePermission(permission);

这句代码真实调用如下:

public class WildcardPermissionResolver implements PermissionResolver {
    public Permission resolvePermission(String permissionString) {
        return new WildcardPermission(permissionString);
    }
}

在上面创建了WildcardPermission实例,由WildcardPermission对象来对permissionString做通配符匹配处理。在WildcardPermission初始化的过程中对permissionString进行了解析。

public WildcardPermission(String wildcardString) {
    //默认请求下对字符串大小写是不敏感的 
	this(wildcardString, DEFAULT_CASE_SENSITIVE);
}

public WildcardPermission(String wildcardString, boolean caseSensitive) {
	setParts(wildcardString, caseSensitive);
}

protected void setParts(String wildcardString, boolean caseSensitive) {
    // 清除字符串前后空格
	wildcardString = StringUtils.clean(wildcardString);

	if (wildcardString == null || wildcardString.isEmpty()) {
		throw new IllegalArgumentException("Wildcard string cannot be null or empty. Make sure permission strings are properly formatted.");
	}
    // 如果大小写不敏感,全部转为小写处理
	if (!caseSensitive) {
		wildcardString = wildcardString.toLowerCase();
	}
    // 首先使用冒号(:)进行一次分隔
	List<String> parts = CollectionUtils.asList(wildcardString.split(PART_DIVIDER_TOKEN));

	this.parts = new ArrayList<Set<String>>();
	for (String part : parts) {
                // 然后再使用逗号(,)进行一次分隔
		Set<String> subparts = CollectionUtils.asSet(part.split(SUBPART_DIVIDER_TOKEN));

		if (subparts.isEmpty()) {
			throw new IllegalArgumentException("Wildcard string cannot contain parts with only dividers. Make sure permission strings are properly formatted.");
		}
        // 将分割后的所有权限信息存储到this.parts中
		this.parts.add(subparts);
	}

	if (this.parts.isEmpty()) {
		throw new IllegalArgumentException("Wildcard string cannot contain only dividers. Make sure permission strings are properly formatted.");
	}
}

接下来就开始判断认证信息AuthorizationInfo中的权限集合是否可以和WildcardPermission进行匹配了。

    public boolean implies(Permission p) {
        //判断类型是否相同
        if (!(p instanceof WildcardPermission)) {
            return false;
        }

        WildcardPermission wp = (WildcardPermission) p;
        // 获取parts属性
        List<Set<String>> otherParts = wp.getParts();

        // otherParts会和getParts()逐一匹配,如果getParts()在匹配过程中比otherParts数量少,就暗指省略可以匹配,返回true
        // 否则的话需要一一进行比较,在比较的过程中如果part不包含通配符(*),且part不能完全包含otherPart集合,就认为没有权限,返回false。
        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++;
            }
        }

        // 处理getParts()比otherParts长的的情况,如果这样,后面部分必须都是通配符(*),否则返回false
        for (; i < getParts().size(); i++) {
            Set<String> part = getParts().get(i);
            if (!part.contains(WILDCARD_TOKEN)) {
                return false;
            }
        }
        return true;
    }

© 著作权归作者所有

xiaoqiyiye
粉丝 22
博文 44
码字总数 80350
作品 0
杭州
私信 提问
Java安全框架「shiro」

以下都是综合之前的人加上自己的一些小总结 Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码学和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用...

技术小能手
2018/10/16
0
0
【shiro】入门程序

Apache Shiro是一个强大的且易用的java安全框架,执行身份验证、授权、密码学和会话管理。使用Shiro的易于理解的API,可以快速,轻松的获得任何应用程序,从最小的移动应用程序到最大的网络和...

binggetong
2017/12/19
0
0
第三章 授权——跟我学习springmvc shiro mybatis

授权,也叫访问控制,即在应用中控制谁能访问哪些资源(如访问页面/编辑数据/页面操作等)。在授权中需了解的几个关键对象:主体(Subject)、资源(Resource)、权限(Permission)、角色(...

CPU滴
2017/06/14
0
0
shiro之授权

一、几个关键对象 主体(Subject) 主体,即访问应用的用户,在Shiro中使用Subject代表该用户。用户只有授权后才允许访问相应的资源。 资源(Resource) 在应用中用户可以访问的任何东西,比...

沉默的懒猫
2016/07/04
356
0
Shiro系列(3) - What is shiro?

什么是shiro? Shiro是apache的一个开源权限管理的框架,它实现用户身份认证,权限授权、加密、会话管理等功能,组成了一个通用的安全认证框架 使用shiro来实现权限管理,可以非常有效的提高...

风间影月
2017/10/25
0
0

没有更多内容

加载失败,请刷新页面

加载更多

001-docker的基础概念

安装 yum install docker;systemctl status docker 我们启动docker的时候,docker会帮我们创建一个docker 0的网桥 docker 基础命令 查看当前镜像 docker images 搜索镜像 docker search 执行...

侠客行之石头
47分钟前
5
0
OSChina 周六乱弹 —— 不要在领导修风扇的时候打开电扇

Osc乱弹歌单(2019)请戳(这里) 【今日歌曲】 @巴拉迪维 :《Whats Up》 主唱妹子 Lina Perry 的嗓音实在太有力了,收放自如的自信才能唱出这么优秀的歌吧!#今日歌曲推荐# 《Whats Up》-...

小小编辑
今天
26
2
SpringBoot集成Elasticsearch并进行增删改查操作

一、创建工程 使用IntelliJ创建SpringBoot工程 SpringBoot版本为2.0.4 ElasticSearch为5.6.10 删掉蓝框中的文件(如上) 最后我们的目录结构(如下) 下面pom文件主要修改的是把spring boot从Int...

一字见心
今天
5
0
聊聊rocketmq的TransientStorePool

序 本文主要研究一下rocketmq的TransientStorePool TransientStorePool rocketmq-all-4.6.0-source-release/store/src/main/java/org/apache/rocketmq/store/TransientStorePool.java publi......

go4it
昨天
6
0
笔记

场外借贷, 质押 ,托管, 永续合约. 场外借贷,n签合同. 新功能 证券交易组负责中信证券机构及个人投资交易相关系统,服务机构及个人投资客户, 涉及到两融、期权、 期货、做市等境内境外创新业...

MtrS
昨天
6
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部