Shiro源码分析-----认证流程/授权流程----------Subject

本文转载自:认证流程和授权流程

源码分析的第二篇以Subject的初始化为题。

一、回顾Apache Shiro创建Subject的步骤如下: 

 //1、获取SecurityManager工厂,此处使用Ini配置文件初始化SecurityManager  
	    Factory<org.apache.shiro.mgt.SecurityManager> factory =  
	            new IniSecurityManagerFactory("classpath:shiro.ini");  
	    //2、得到SecurityManager实例 并绑定给SecurityUtils  
	    org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance();  
	    SecurityUtils.setSecurityManager(securityManager);  
	    //3、得到Subject及创建用户名/密码身份验证Token(即用户身份/凭证)  
	    Subject subject = SecurityUtils.getSubject(); 

Apache Shiro 创建Subject 规则如下:

首先通过new IniSecurityManagerFactory并指定一个ini配置文件来创建一个SecurityManager工厂;

接着获取SecurityManager并绑定到SecurityUtils,这是一个全局设置,设置一次即可;

通过SecurityUtils得到Subject,其会自动绑定到当前线程;


接口Subject的文档介绍如下:

外界通过Subject接口来和SecurityManager进行交互,该接口含有登录、退出、权限判断、获取session,其中的Session可不是平常我们所使用的HttpSession等,而是shiro自定义的,是一个数据上下文,与一个Subject相关联的。

Apache Shiro 源代码创建步骤如下:

1、org.apache.shiro.Subject类的静态方法getSubject();

 public static Subject getSubject()
    {
        Subject subject = ThreadContext.getSubject();
        if(subject == null)
        {
            subject = (new org.apache.shiro.subject.Subject.Builder()).buildSubject();
            ThreadContext.bind(subject);
        }
        return subject;
    }
一看就是使用的是ThreadLocal设计模式,获取当前线程相关联的Subject 对象,如果没有则创建一个,然后绑定到当前线程。


2、ThreadContext是org.apache.shiro.util包下的一个工具类,它是用来操作和当前线程绑定的SecurityManager和Subject,它必然包含了一个ThreadLocal对象如下:

 private static final Logger log = LoggerFactory.getLogger(org/apache/shiro/util/ThreadContext);
    public static final String SECURITY_MANAGER_KEY = (new StringBuilder()).append(org/apache/shiro/util/ThreadContext.getName()).append("_SECURITY_MANAGER_KEY").toString();
    public static final String SUBJECT_KEY = (new StringBuilder()).append(org/apache/shiro/util/ThreadContext.getName()).append("_SUBJECT_KEY").toString();
    private static final ThreadLocal resources = new InheritableThreadLocalMap();
    /*省略*/
ThreadLocal中所存放的数据是一个Map集合,集合中所存的key有两个SECURITY_MANAGER_KEY 和SUBJECT_KEY ,就是通过这两个key来存取SecurityManager和Subject两个对象的。


3、当前线程还没有绑定一个Subject时,就需要通过Subject.Builder来创建一个然后绑定到当前线程。Builder是Subject的一个内部类,它拥有两个重要的属性,SubjectContext和SecurityManager,创建Builder时使用SecurityUtils工具来获取它的全局静态变量SecurityManager,SubjectContext则是使用newSubjectContextInstance创建一个DefaultSubjectContext对象:

  public Builder()
        {
            this(SecurityUtils.getSecurityManager());
        }

        public Builder(SecurityManager securityManager)
        {
            if(securityManager == null)
                throw new NullPointerException("SecurityManager method argument cannot be null.");
            this.securityManager = securityManager;
            subjectContext = newSubjectContextInstance();
            if(subjectContext == null)
            {
                throw new IllegalStateException("Subject instance returned from 'newSubjectContextInstance' cannot be null.");
            } else
            {
                subjectContext.setSecurityManager(securityManager);
                return;
            }
        }
    }

4、 Builder准备工作完成后,调用buildSubject来创建一个Subject:

  public Subject buildSubject()
        {
            return securityManager.createSubject(subjectContext);
        }
最终还是通过securityManager根据subjectContext来创建一个Subject。最终是通过一个SubjectFactory来创建的,SubjectFactory是一个接口,接口方法为Subject createSubject(SubjectContext context),默认的SubjectFactory实现是DefaultSubjectFactory,DefaultSubjectFactory创建的Subject是DelegatingSubject。【至此,我们通过源码分析已知Subject创建流程


5、调用Subject.isPermitted/hasRole方法,其本质是委托给SecurityManager的hasRole方法。

   public boolean hasRole(String roleIdentifier)
    {
        return hasPrincipals() && securityManager.hasRole(getPrincipals(), roleIdentifier);
    }


6、 AuthorizingSecurityManager实现了Authorizer接口,但是AuthorizingSecurityManager是通过内部Authorizer引用来完成具体的功能,默认采用的是ModularRealmAuthorizer。如下:
public abstract class AuthorizingSecurityManager extends AuthenticatingSecurityManager
{

    public AuthorizingSecurityManager()
    {
        authorizer = new ModularRealmAuthorizer();
    }
     public boolean hasRole(PrincipalCollection principals, String roleIdentifier)
    {
        return authorizer.hasRole(principals, roleIdentifier);
    }
    /*省略*/
}
来看看这个Authorizer模块的接口设计:
/*jadclipse*/// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3) radix(10) lradix(10) 
// Source File Name:   Authorizer.java

package org.apache.shiro.authz;

import java.util.Collection;
import java.util.List;
import org.apache.shiro.subject.PrincipalCollection;

// Referenced classes of package org.apache.shiro.authz:
//            AuthorizationException, Permission

public interface Authorizer
{

    public abstract boolean isPermitted(PrincipalCollection principalcollection, String s);

    public abstract boolean isPermitted(PrincipalCollection principalcollection, Permission permission);

    public transient abstract boolean[] isPermitted(PrincipalCollection principalcollection, String as[]);

    public abstract boolean[] isPermitted(PrincipalCollection principalcollection, List list);

    public transient abstract boolean isPermittedAll(PrincipalCollection principalcollection, String as[]);

    public abstract boolean isPermittedAll(PrincipalCollection principalcollection, Collection collection);

    public abstract void checkPermission(PrincipalCollection principalcollection, String s)
        throws AuthorizationException;

    public abstract void checkPermission(PrincipalCollection principalcollection, Permission permission)
        throws AuthorizationException;

    public transient abstract void checkPermissions(PrincipalCollection principalcollection, String as[])
        throws AuthorizationException;

    public abstract void checkPermissions(PrincipalCollection principalcollection, Collection collection)
        throws AuthorizationException;

    public abstract boolean hasRole(PrincipalCollection principalcollection, String s);

    public abstract boolean[] hasRoles(PrincipalCollection principalcollection, List list);

    public abstract boolean hasAllRoles(PrincipalCollection principalcollection, Collection collection);

    public abstract void checkRole(PrincipalCollection principalcollection, String s)
        throws AuthorizationException;

    public abstract void checkRoles(PrincipalCollection principalcollection, Collection collection)
        throws AuthorizationException;

    public transient abstract void checkRoles(PrincipalCollection principalcollection, String as[])
        throws AuthorizationException;
}


/*
	DECOMPILATION REPORT

	Decompiled from: E:\webapp2\workspace\ShiroAuthentication\lib\shiro-core-1.2.4.jar
	Total time: 56 ms
	Jad reported messages/errors:
	Exit status: 0
	Caught exceptions:
*/
从上面的接口中,可以分成两大类,第一类是验证用户的某个或某些权限,第二类是验证用户的某个角色或某些角色。角色则是一组权限的集合,所以后者是粗粒度的验证,而前者是细粒度的验证。对于那些check方法则是验证不通过时抛出异常。
接口实现类图为:



可以看到很多的Realm都实现了该接口,即这些Realm不仅提供登陆验证,还提供权限验证。
先来看下默认使用的ModularRealmAuthorizer:

public class ModularRealmAuthorizer implements Authorizer, PermissionResolverAware, RolePermissionResolverAware {
    protected Collection realms;

    protected PermissionResolver permissionResolver;

    protected RolePermissionResolver rolePermissionResolver;
    //略
}
可以看到,它有三个重要属性,Realm集合和PermissionResolver 、RolePermissionResolver 。PermissionResolver 是什么呢?

package org.apache.shiro.authz.permission;

import org.apache.shiro.authz.Permission;

public interface PermissionResolver
{

    public abstract Permission resolvePermission(String s);
}
就是将权限字符串解析成Permission 对象,同理RolePermissionResolver 如下:
package org.apache.shiro.authz.permission;

import java.util.Collection;

public interface RolePermissionResolver
{

    public abstract Collection resolvePermissionsInRole(String s);
}
将角色字符串解析成Permission 集合


我们首先来看看ModularRealmAuthorizer类的几个方法:

public ModularRealmAuthorizer(Collection<Realm> realms) {
        setRealms(realms);
    }
public void setRealms(Collection<Realm> realms) {
        this.realms = realms;
        applyPermissionResolverToRealms();
        applyRolePermissionResolverToRealms();
    }
public void setPermissionResolver(PermissionResolver permissionResolver) {
        this.permissionResolver = permissionResolver;
        applyPermissionResolverToRealms();
    }
public void setRolePermissionResolver(RolePermissionResolver rolePermissionResolver) {
        this.rolePermissionResolver = rolePermissionResolver;
        applyRolePermissionResolverToRealms();
    }
protected void applyRolePermissionResolverToRealms() {
        RolePermissionResolver resolver = getRolePermissionResolver();
        Collection<Realm> realms = getRealms();
        if (resolver != null && realms != null && !realms.isEmpty()) {
            for (Realm realm : realms) {
                if (realm instanceof RolePermissionResolverAware) {
                    ((RolePermissionResolverAware) realm).setRolePermissionResolver(resolver);
                }
            }
        }
    }
protected void applyPermissionResolverToRealms() {
        PermissionResolver resolver = getPermissionResolver();
        Collection<Realm> realms = getRealms();
        if (resolver != null && realms != null && !realms.isEmpty()) {
            for (Realm realm : realms) {
                if (realm instanceof PermissionResolverAware) {
                    ((PermissionResolverAware) realm).setPermissionResolver(resolver);
                }
            }
        }
    }
以上这几个方法,其目的都是如果哪些Realm 想要PermissionResolver 或RolePermissionResolver 参数,则将ModularRealmAuthorizer 的对应参数传给它。

7、我们再来看看ModularRealmAuthorizer 是如何实现Authorizer接口的:

protected void assertRealmsConfigured() throws IllegalStateException {
        Collection<Realm> realms = getRealms();
        if (realms == null || realms.isEmpty()) {
            String msg = "Configuration error:  No realms have been configured!  One or more realms must be " +
                    "present to execute an authorization operation.";
            throw new IllegalStateException(msg);
        }
    }
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;
    }
首先是判断Collection<Realm> realms集合是否为空,然后就是将那些实现了Authorizer接口的Realm 来判断是否具有某个权限,也就是ModularRealmAuthorizer本身并不去权限验证,而是交给那些具有权限验证功能的Realm去验证(即那些Realm实现了Authorizer接口)。所以
ModularRealmAuthorizer并不具有太多实际内容,我们转战那些实现了Authorizer接口的Realm,去看看他们的验证过程。
这时候,就需要看Authorizer接口的另一个分支即下图AuthorizingRealm分支:


AuthorizingRealm涉及到Realm,所以再把Realm说清楚。Realm接口如下

public interface Realm {
    String getName();
    boolean supports(AuthenticationToken token);
    AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException;
}
Realm 本身只具有验证用户是否合法的功能,不具有授权的功能。


AuthenticatingRealm及其子类主要完成认证流程,首先是其的初始化,AuthenticatingRealm及其子类都实现了Initializable接口,初始化的时候会首先获取其缓存,如下:

public final void init() {
        //trigger obtaining the authorization cache if possible
        getAvailableAuthenticationCache();
        onInit();
    }
private Cache<Object, AuthenticationInfo> getAvailableAuthenticationCache() {
        Cache<Object, AuthenticationInfo> cache = getAuthenticationCache();
        boolean authcCachingEnabled = isAuthenticationCachingEnabled();
        if (cache == null && authcCachingEnabled) {
            cache = getAuthenticationCacheLazy();
        }
        return cache;
    }
private Cache<Object, AuthenticationInfo> getAuthenticationCacheLazy() {

        if (this.authenticationCache == null) {

            log.trace("No authenticationCache instance set.  Checking for a cacheManager...");

            CacheManager cacheManager = getCacheManager();

            if (cacheManager != null) {
                String cacheName = getAuthenticationCacheName();
                log.debug("CacheManager [{}] configured.  Building authentication cache '{}'", cacheManager, cacheName);
                this.authenticationCache = cacheManager.getCache(cacheName);
            }
        }

        return this.authenticationCache;
    }
首先会获取Cache<Object, AuthenticationInfo> cache属性,如果没有,再判断是否允许缓存,如果允许,则通过CacheManager 来获取,之前已分析过,如果还没有则会创建一个Cache,然后返回。
再看下认证过程如下:

public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        AuthenticationInfo info = getCachedAuthenticationInfo(token);
        if (info == null) {
            //otherwise not cached, perform the lookup:
            info = doGetAuthenticationInfo(token);
            log.debug("Looked up AuthenticationInfo [{}] from doGetAuthenticationInfo", info);
            if (token != null && info != null) {
                cacheAuthenticationInfoIfPossible(token, info);
            }
        } else {
            log.debug("Using cached authentication info [{}] to perform credentials matching.", info);
        }

        if (info != null) {
            assertCredentialsMatch(token, info);
        } else {
            log.debug("No AuthenticationInfo found for submitted AuthenticationToken [{}].  Returning null.", token);
        }

        return info;
    }
首先从缓存中尝试是否能找到AuthenticationInfo ,如果找不到,则需要子类去完成具体的认证细节,然后再存储到缓存中,因为本类并没有具体的数据源,只有缓存源,所以本类只是搭建了认证流程,具体的认证细节则由具体的子类来完成,所以 doGetAuthenticationInfo(token)是一个protected的抽象方法,如下:

protected abstract AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException;
当缓存中存在或者子类进行具体的认证后,下一步的操作是要进行密码匹配的过程,AuthenticatingRealm有一个属性CredentialsMatcher credentialsMatcher,接口如下:

public interface CredentialsMatcher {
    boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info);
}
就是匹配我们认证时的AuthenticationToken 和刚才已找到的AuthenticationInfo 是否匹配。有如下的实现类:AllowAllCredentialsMatcher、PasswordMatcher、SimpleCredentialsMatcher等等。AuthenticatingRealm的构造函数默认使用的是SimpleCredentialsMatcher:

public AuthenticatingRealm() {
        this(null, new SimpleCredentialsMatcher());
    }

    public AuthenticatingRealm(CacheManager cacheManager) {
        this(cacheManager, new SimpleCredentialsMatcher());
    }

    public AuthenticatingRealm(CredentialsMatcher matcher) {
        this(null, matcher);
    }
这一块内容先暂时不讲,后续文章再来详细说明。
当你匹配通过了,则就算认证成功了。认证流程就在AuthenticatingRealm中完成了。

我们再向它的子类AuthorizingRealm研究,这个就有涉及到授权的功能了。AuthenticatingRealm是将整个认证流程框架化,AuthorizingRealm则是将整个授权流程框架化,AuthorizingRealm也有授权缓存,所以会通过父类CachingRealm来获取CacheManager,同时也有一个子缓存开关authorizationCachingEnabled,和AuthenticatingRealm基本类似,属性如下:

public abstract class AuthorizingRealm extends AuthenticatingRealm
        implements Authorizer, Initializable, PermissionResolverAware, RolePermissionResolverAware {

    private static final String DEFAULT_AUTHORIZATION_CACHE_SUFFIX = ".authorizationCache";

    private static final AtomicInteger INSTANCE_COUNT = new AtomicInteger();

    private boolean authorizationCachingEnabled;
    private Cache<Object, AuthorizationInfo> authorizationCache;
    private String authorizationCacheName;

    private PermissionResolver permissionResolver;

    private RolePermissionResolver permissionRoleResolver;

    public AuthorizingRealm() {
        this(null, null);
    }

    public AuthorizingRealm(CacheManager cacheManager) {
        this(cacheManager, null);
    }

    public AuthorizingRealm(CredentialsMatcher matcher) {
        this(null, matcher);
    }

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

AtomicInteger 同样是用于对那些具有授权功能的Realm进行数量统计的,authorizationCachingEnabled缓存子开关,authorizationCache缓存,authorizationCacheName缓存名字。 PermissionResolver permissionResolver、RolePermissionResolver permissionRoleResolver这两个则是对字符串进行解析对应的Permission和Collection<Permission>的。我们来看下AuthorizingRealm的主要功能,对于授权接口Authorizer的实现:

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

首先就是获取授权信息,看下getAuthorizationInfo:

protected AuthorizationInfo getAuthorizationInfo(PrincipalCollection principals) {

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

        AuthorizationInfo info = null;

        if (log.isTraceEnabled()) {
            log.trace("Retrieving AuthorizationInfo for principals [" + principals + "]");
        }

        Cache<Object, AuthorizationInfo> cache = getAvailableAuthorizationCache();
        if (cache != null) {
            if (log.isTraceEnabled()) {
                log.trace("Attempting to retrieve the AuthorizationInfo from cache.");
            }
            Object key = getAuthorizationCacheKey(principals);
            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 + "]");
                }
            }
        }

        if (info == null) {
            // Call template method if the info was not found in a cache
            info = doGetAuthorizationInfo(principals);
            // If the info is not null and the cache has been created, then cache the authorization info.
            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;
    }

同样很容易理解,先得到缓存,从缓存中去找有没有授权信息,如果没有,则需要子类去完成具体的授权细节即doGetAuthorizationInfo,授权完成后放置缓存中。同样doGetAuthorizationInfo是protected的抽象方法,由子类去实现。PermissionResolver permissionResolver、RolePermissionResolver permissionRoleResolver则是发挥如下作用:

private Collection<Permission> getPermissions(AuthorizationInfo info) {
        Set<Permission> permissions = new HashSet<Permission>();

        if (info != null) {
            Collection<Permission> perms = info.getObjectPermissions();
            if (!CollectionUtils.isEmpty(perms)) {
                permissions.addAll(perms);
            }
            perms = resolvePermissions(info.getStringPermissions());
            if (!CollectionUtils.isEmpty(perms)) {
                permissions.addAll(perms);
            }

            perms = resolveRolePermissions(info.getRoles());
            if (!CollectionUtils.isEmpty(perms)) {
                permissions.addAll(perms);
            }
        }

        if (permissions.isEmpty()) {
            return Collections.emptySet();
        } else {
            return Collections.unmodifiableSet(permissions);
        }
    }

即有了授权信息AuthorizationInfo 后,获取所有的权限Permission,有三种途径来收集,第一种就是info.getObjectPermissions() info中直接含有Permission对象集合,第二种就是info.getStringPermissions() info中有字符串形式的权限表示,第三种就是info.getRoles() info中含有角色集合,角色也是一组权限的集合,看下resolvePermissions(info.getStringPermissions()):

private Collection<Permission> resolvePermissions(Collection<String> stringPerms) {
        Collection<Permission> perms = Collections.emptySet();
        PermissionResolver resolver = getPermissionResolver();
        if (resolver != null && !CollectionUtils.isEmpty(stringPerms)) {
            perms = new LinkedHashSet<Permission>(stringPerms.size());
            for (String strPermission : stringPerms) {
                Permission permission = getPermissionResolver().resolvePermission(strPermission);
                perms.add(permission);
            }
        }
        return perms;
    }

也很简单,对于每一个strPermission 通过PermissionResolver 转化成Permission 对象,对于resolveRolePermissions也同理,不再说明。这里具体的转化细节先暂且不说,后续再将。
现在终于把认证流程和授权框架流程大致说完了,即AuthenticatingRealm和AuthorizingRealm的内容,他们分别留给子类protected abstract AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException;具体的认证方法和protected abstract AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals)具体的授权方法。












  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值