《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 https://my.oschina.net/xiaoqiyiye/blog/1618724 「xiaoqiyiye」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

本文在于分析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;
}
文章目录
  1. 1. isPermitted方法分析
  2. 2. hasRole方法分析
  • WildcardPermission分析