Apache Shiro ⒉认证和授权逻辑概述

本篇主要介绍如何快速开始 Shiro 之旅, 并涉及部分源码解析 (包含 Shiro 的基本工作机制).
相关源码参考: demo-shiro-quickstart

ShiroConfiguration

这是开发者定义的 Shiro 的核心配置类, 在其中我们需要完成 Realm, ShiroFilterFactoryBeanWebSecurityManager 的注入.

PART#1: WebSecurityManager

首先, WebSecurityManager 继承自 SecurityManager, 后者是 Shiro 的核心组件. 所有的交互都是通过 SecurityManager 进行控制, 它管理着所有 Subject, 且负责进行认证 (Authenticator) 和授权 (Authorizer) 及会话缓存 (SessionManager) 的管理.

如果开发者仅仅注入 WebSecurityManager, 启动时会报错:

Description:
       Parameter 0 of method authorizationAttributeSourceAdvisor in org.apache.shiro.spring.boot.autoconfigure.ShiroAnnotationProcessorAutoConfiguration required a bean named 'authorizer' that could not be found.
     Action:
       Consider defining a bean named 'authorizer' in your configuration.

而 ShiroAnnotationProcessorAutoConfiguration#authorizationAttributeSourceAdvisor(SecurityManager) 方法的目的是在上下文中注入一个 AuthorizationAttributeSourceAdvisor, 请看 ShiroAnnotationProcessorAutoConfiguration 的部分代码摘录 ↓

@Bean
@ConditionalOnMissingBean
@Override
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
    return super.authorizationAttributeSourceAdvisor(securityManager);
}

我们先来看看 AuthorizationAttributeSourceAdvisor

AuthorizationAttributeSourceAdvisor

以往, 在 spring-shiro.xml 中通常会加上一个 aop 配置, 以使 @RequiresPermission, @RequiresRoles, @RequiresUser@RequiresGuest 生效, 配置通常形如:

<aop:config />
<!--权限注解的 advisor -->
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
    <property name="securityManager" ref="securityManager"/>
</bean>

但是 <aop:config /> 中并没有配置通知, 也没有配置切入点, Shiro 的认证注解怎么就起作用了呢?

这里就不得不提到 AuthorizationAttributeSourceAdvisor 这个 “通知器”. 通过其层次结构我们看到, 它实现了 org.springframework.aop.Pointcut 接口, 后者是 Spring 的 “切入点” 抽象 “标准 (接口)”. 而在 AuthorizationAttributeSourceAdvisor 的父类 StaticMethodMatcherPointcut 实现了 Pointcut 的接口方法:

public abstract class StaticMethodMatcherPointcut extends StaticMethodMatcher implements Pointcut {
	private ClassFilter classFilter = ClassFilter.TRUE;
    
	public void setClassFilter(ClassFilter classFilter) {
		this.classFilter = classFilter;
	}

	@Override
	public ClassFilter getClassFilter() {
		return this.classFilter;
	}

	@Override
	public final MethodMatcher getMethodMatcher() {
		return this;
	}
}

我们注意到 classFilter 的值是 ClassFilter.TRUE, 跟进去看到说明是 “Canonical instance of a ClassFilter that matches all classes”, 意味着 AuthorizationAttributeSourceAdvisor匹配所有类.

再来看 getMethodMatcher(), 返回的是 this, 而 AuthorizationAttributeSourceAdvisor 实现了 MethodMatchaer, 所有返回的就是 AuthorizationAttributeSourceAdvisor. MethodMatcherboolean matches(Method method, Class<?> targetClass, Object... args) 用来判断方法匹配. 而在 AuthorizationAttributeSourceAdvisor 的 matches 实现中:

public boolean matches(Method method, Class targetClass) {
    Method m = method;
    if ( isAuthzAnnotationPresent(m) ) {
        return true;
    }

    if ( targetClass != null) {
        try {
            m = targetClass.getMethod(m.getName(), m.getParameterTypes());
            return isAuthzAnnotationPresent(m) || isAuthzAnnotationPresent(targetClass);
        } catch (NoSuchMethodException ignored) {

        }
    }
    return false;
}

可以得出: AuthorizationAttributeSourceAdvisor 会拦截所有类中加了认证注解的方法.

PART#2: ShiroFilterFactoryBean

再来看看 ShiroConfiguration 注入的第二个 Bean: org.apache.shiro.spring.web.ShiroFilterFactoryBean…
ShiroFilterFactoryBean 是创建 ShiroFilterFactoryBean. Shiro 通过后者来拦截需要安全拦截的 URL 以实现访问控制. 获取 ShiroFilter 的方式则是通过这个工厂 Bean 的 getObject() 方法, 而在 getObject() 方法中, 又调用了 createInstance(), 我们来看 createInstance() 的实现:

public class ShiroFilterFactoryBean implements FactoryBean, BeanPostProcessor {
    ...
    protected FilterChainManager createFilterChainManager() {

        DefaultFilterChainManager manager = new DefaultFilterChainManager();
        Map<String, Filter> defaultFilters = manager.getFilters();
        //apply global settings if necessary:
        for (Filter filter : defaultFilters.values()) {
            applyGlobalPropertiesIfNecessary(filter);
        }

        //Apply the acquired and/or configured filters:
        Map<String, Filter> filters = getFilters();
        if (!CollectionUtils.isEmpty(filters)) {
            for (Map.Entry<String, Filter> entry : filters.entrySet()) {
                String name = entry.getKey();
                Filter filter = entry.getValue();
                applyGlobalPropertiesIfNecessary(filter);
                if (filter instanceof Nameable) {
                    ((Nameable) filter).setName(name);
                }
                manager.addFilter(name, filter, false);
            }
        }

        // set the global filters
        manager.setGlobalFilters(this.globalFilters);

        //build up the chains:
        Map<String, String> chains = getFilterChainDefinitionMap();
        if (!CollectionUtils.isEmpty(chains)) {
            for (Map.Entry<String, String> entry : chains.entrySet()) {
                String url = entry.getKey();
                String chainDefinition = entry.getValue();
                manager.createChain(url, chainDefinition);
            }
        }

        // create the default chain, to match anything the path matching would have missed
        manager.createDefaultChain("/**");

        return manager;
    }
    
    ...

    protected AbstractShiroFilter createInstance() throws Exception {
        log.debug("Creating Shiro Filter instance.");

        SecurityManager securityManager = getSecurityManager();
        if (securityManager == null) {
            String msg = "SecurityManager property must be set.";
            throw new BeanInitializationException(msg);
        }

        if (!(securityManager instanceof WebSecurityManager)) {
            String msg = "The security manager does not implement the WebSecurityManager interface.";
            throw new BeanInitializationException(msg);
        }

        FilterChainManager manager = createFilterChainManager();

        PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();
        chainResolver.setFilterChainManager(manager);

        return new SpringShiroFilter((WebSecurityManager) securityManager, chainResolver);
    }
    ...
}

可以看到创建 SpringShiroFilter 用到了两个组件: WebSecurityManagerPathMatchingFilterChainResolver. 前者在上一节已经介绍过, 我们重点看后者.

FilterChainResolver

PathMatchingFilterChainResolverFilterChainResolver 的实现类, 能够在一次 ServletRequest 中解析 javax.servlet.FilterChain. 只有一个接口方法: FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain), 返回一个对于当前请求来说, 应当被执行的过滤器链否则返回 null (应用 originalChain). PathMatchingFilterChainResolver 实现了对于 PatternMatcher 的支持 (默认是 org.apache.shiro.util.AntPathMatcher), 让过滤器链解析器拥有通配符匹配的能力并且会在实现接口的方法 getChain 中, 应用 pathMatcher,

public class PathMatchingFilterChainResolver implements FilterChainResolver {
    ...
    public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) {
        FilterChainManager filterChainManager = getFilterChainManager();
        if (!filterChainManager.hasChains()) {
            return null;
        }

        final String requestURI = getPathWithinApplication(request);
        final String requestURINoTrailingSlash = removeTrailingSlash(requestURI);

        //the 'chain names' in this implementation are actually path patterns defined by the user.  We just use them
        //as the chain name for the FilterChainManager's requirements
        for (String pathPattern : filterChainManager.getChainNames()) {
            // If the path does match, then pass on to the subclass implementation for specific checks:
            if (pathMatches(pathPattern, requestURI)) {
                ...
                return filterChainManager.proxy(originalChain, pathPattern);
            } else {
                // in spring web, the requestURI "/resource/menus" ---- "resource/menus/" bose can access the resource
                // but the pathPattern match "/resource/menus" can not match "resource/menus/"
                // user can use requestURI + "/" to simply bypassed chain filter, to bypassed shiro protect
                pathPattern = removeTrailingSlash(pathPattern);

                if (pathMatches(pathPattern, requestURINoTrailingSlash)) {
                    ...
                    return filterChainManager.proxy(originalChain, requestURINoTrailingSlash);
                }
            }
        }
        return null;
    }
    ...
}

FilterChainManager

从上面摘录的 PathMatchingFilterChainResolver#getChain 代码中可以看到, ChainResolver 还包含了 1 个核心组件: FilterChainManager. 从 ShiroFilterFactoryBean#createInstance() 方法中的 new PathMatchingFilterChainResolver() 跟进去可以看到:

public class PathMatchingFilterChainResolver implements FilterChainResolver {
    ...
    public PathMatchingFilterChainResolver() {
        this.pathMatcher = new AntPathMatcher();
        this.filterChainManager = new DefaultFilterChainManager();
    }
    ...
}

PathMatchingFilterChainResolver#getChain 方法体中 FilterChainManager 的默认实现是 org.apache.shiro.web.filter.mgt.DefaultFilterChainManager, 其作用是管理 Filter 和Filter 链 (维护着一个过滤器的 Map 以及过滤器链表的 Map),配合 PathMatchingFilterChainResolver 解析出 Filter 链. 接下来我们来看看这个 DefaultFilterChainManager 的实现:

public class DefaultFilterChainManager implements FilterChainManager {
    ...
    public DefaultFilterChainManager() {
        this.filters = new LinkedHashMap<String, Filter>();
        this.filterChains = new LinkedHashMap<String, NamedFilterList>();
        this.globalFilterNames = new ArrayList<>();
        addDefaultFilters(false);
    }

    public DefaultFilterChainManager(FilterConfig filterConfig) {
        this.filters = new LinkedHashMap<String, Filter>();
        this.filterChains = new LinkedHashMap<String, NamedFilterList>();
        this.globalFilterNames = new ArrayList<>();
        setFilterConfig(filterConfig);
        addDefaultFilters(true);
    }
    ...
    #filter#    
    protected void addFilter(String name, Filter filter, boolean init, boolean overwrite) {
        Filter existing = getFilter(name);
        if (existing == null || overwrite) {
            if (filter instanceof Nameable) {
                ((Nameable) filter).setName(name);
            }
            if (init) {
                initFilter(filter);
            }
            this.filters.put(name, filter);
        }
    }
    ...
        
    protected void addDefaultFilters(boolean init) {
        for (DefaultFilter defaultFilter : DefaultFilter.values()) {
            addFilter(defaultFilter.name(), defaultFilter.newInstance(), init, false);
        }
    }
    ...
    #chain#
    public void createChain(String chainName, String chainDefinition) {
        ...
        // first add each of global filters
        if (!CollectionUtils.isEmpty(globalFilterNames)) {
            globalFilterNames.stream().forEach(filterName -> addToChain(chainName, filterName));
        }
        ...
        String[] filterTokens = splitChainDefinition(chainDefinition);

        //each token is specific to each filter.
        //strip the name and extract any filter-specific config between brackets [ ]
        for (String token : filterTokens) {
            String[] nameConfigPair = toNameConfigPair(token);

            //now we have the filter name, path and (possibly null) path-specific config.  Let's apply them:
            addToChain(chainName, nameConfigPair[0], nameConfigPair[1]);
        }
    }
        
    public void addToChain(String chainName, String filterName, String chainSpecificFilterConfig) {
        if (!StringUtils.hasText(chainName)) {
            throw new IllegalArgumentException("chainName cannot be null or empty.");
        }
        Filter filter = getFilter(filterName);
        ...

        applyChainConfig(chainName, filter, chainSpecificFilterConfig);

        NamedFilterList chain = ensureChain(chainName);
        chain.add(filter);
    }
    
    protected NamedFilterList ensureChain(String chainName) {
        NamedFilterList chain = getChain(chainName);
        if (chain == null) {
            chain = new SimpleNamedFilterList(chainName);
            this.filterChains.put(chainName, chain);
        }
        return chain;
    }
    ...
    #global#
    public void setGlobalFilters(List<String> globalFilterNames) throws ConfigurationException {
        // validate each filter name
        if (!CollectionUtils.isEmpty(globalFilterNames)) {
            for (String filterName : globalFilterNames) {
                Filter filter = filters.get(filterName);
                ...
                this.globalFilterNames.add(filterName);
            }
        }
    }
}

关于 DefaultFilterChainManagerfilters, 可以看到, 在构造 DefaultFilterChainManager 的时候, 会首先默认从 DefaultFilter 这个枚举类中将默认的 Filter 添加到 filters 中 (关于 DefaultFilter 以及自定义 Filter, 会在本系列稍后的文章中介绍到), filters 维护着一个过滤器的池, 其中的过滤器用于创建过滤器链.

关于 DefaultFilterChainManagerglobalFilterNames, 它持有的是对于每一个过滤器链都启用的过滤器的名字, 它在 setGlobalFilters 中被初始化, 而 setGlobalFilters 在 ShiroFilterFactoryBean 的 createInstance 方法中创建 FilterCharinManager 时被调用.

public class ShiroFilterFactoryBean implements FactoryBean, BeanPostProcessor {
    ...
    protected FilterChainManager createFilterChainManager() {
        DefaultFilterChainManager manager = new DefaultFilterChainManager();
        Map<String, Filter> defaultFilters = manager.getFilters();
        //apply global settings if necessary:
        for (Filter filter : defaultFilters.values()) {
            applyGlobalPropertiesIfNecessary(filter);
        }

        //Apply the acquired and/or configured filters:
        Map<String, Filter> filters = getFilters();
        if (!CollectionUtils.isEmpty(filters)) {
            for (Map.Entry<String, Filter> entry : filters.entrySet()) {
                String name = entry.getKey();
                Filter filter = entry.getValue();
                applyGlobalPropertiesIfNecessary(filter);
                if (filter instanceof Nameable) {
                    ((Nameable) filter).setName(name);
                }
                //'init' argument is false, since Spring-configured filters should be initialized
                //in Spring (i.e. 'init-method=blah') or implement InitializingBean:
                manager.addFilter(name, filter, false);
            }
        }

        // set the global filters
        manager.setGlobalFilters(this.globalFilters);

        //build up the chains:
        Map<String, String> chains = getFilterChainDefinitionMap();
        if (!CollectionUtils.isEmpty(chains)) {
            for (Map.Entry<String, String> entry : chains.entrySet()) {
                String url = entry.getKey();
                String chainDefinition = entry.getValue();
                manager.createChain(url, chainDefinition);
            }
        }

        // create the default chain, to match anything the path matching would have missed
        manager.createDefaultChain("/**"); // TODO this assumes ANT path matching, which might be OK here
        return manager;
    }    
    ...
}

~ 构建 filterChains 的过程: DefaultFilterChainManagerfilterChains 维护的是过滤器链名 (实际上就是 URL 的 pattern) 和过滤器的链表的映射关系. 从上面的 ShiroFilterFactoryBean 创建 FilterChainManager 的方法片段也可以看出, filterChains 是在 ShiroFilterFactoryBeancreateFilterChainManager 中通过接口方法 FilterChainManager#createChain(url, chainDefinition) 构建的.

再来看看构建过滤器链的 “数据源”: getFilterChainDefinitionMap(): 它是 ShiroFilterFactoryBean 的成员属性: filterChainDefinitionMap, 默认是空 Map, 由开发者指定.


紧接着, Shiro 又创建了一个匹配所有 URL (/**) 的默认过滤器链:

public class DefaultFilterChainManager implements FilterChainManager {
    ...
    public void createDefaultChain(String chainName) {
        // only create the defaultChain if we don't have a chain with this name already
        // (the global filters will already be in that chain)
        if (!getChainNames().contains(chainName) && !CollectionUtils.isEmpty(globalFilterNames)) {
            // add each of global filters
            globalFilterNames.stream().forEach(filterName -> addToChain(chainName, filterName));
        }
    }    
    ...
}

从上面代码片段可以看出, Shiro 将 globalFilterNames 中的过滤器关联到了 /**. 而 globalFilterNames 的初始化在 ShiroFilterFactoryBean:

public class ShiroFilterFactoryBean implements FactoryBean, BeanPostProcessor {
    ...
    public ShiroFilterFactoryBean() {
        this.filters = new LinkedHashMap<String, Filter>();
        this.globalFilters = new ArrayList<>();
        this.globalFilters.add(DefaultFilter.invalidRequest.name());
        this.filterChainDefinitionMap = new LinkedHashMap<String, String>();
    }
    ...
}

最后, 回到 DefaultFilterChainManagercreateChain 看 filterChains 是怎么构建的:

public class DefaultFilterChainManager implements FilterChainManager {
	...
	public void createChain(String chainName, String chainDefinition) {
        ...
        // first add each of global filters
        if (!CollectionUtils.isEmpty(globalFilterNames)) {
            globalFilterNames.stream().forEach(filterName -> addToChain(chainName, filterName));
        }
        ...
        //parse the value by tokenizing it to get the resulting filter-specific config entries
        //
        //e.g. for a value of
        //
        //     "authc, roles[admin,user], perms[file:edit]"
        //
        // the resulting token array would equal
        //
        //     { "authc", "roles[admin,user]", "perms[file:edit]" }
        String[] filterTokens = splitChainDefinition(chainDefinition);

        //each token is specific to each filter.
        //strip the name and extract any filter-specific config between brackets [ ]
        for (String token : filterTokens) {
            String[] nameConfigPair = toNameConfigPair(token);

            //now we have the filter name, path and (possibly null) path-specific config.  Let's apply them:
            addToChain(chainName, nameConfigPair[0], nameConfigPair[1]);
        }
    }
    ... 
    public void addToChain(String chainName, String filterName) {
        addToChain(chainName, filterName, null);
    }

    public void addToChain(String chainName, String filterName, String chainSpecificFilterConfig) {
        ...
        Filter filter = getFilter(filterName);
        ...
        applyChainConfig(chainName, filter, chainSpecificFilterConfig);

        NamedFilterList chain = ensureChain(chainName);
        chain.add(filter);
    }
	...
    protected NamedFilterList ensureChain(String chainName) {
        NamedFilterList chain = getChain(chainName);
        if (chain == null) {
            chain = new SimpleNamedFilterList(chainName);
            this.filterChains.put(chainName, chain);
        }
        return chain;
    }
    ...
}

综上, 初始化 (构建 ShiroFilter) 时, Shiro 会以 DefaultFilter.invalidRequest 来构建一个 /** 的过滤器链, 并根据开发者指定的 filterChainDefinitionMap 来构建用户自定义的过滤器链.

SpringShiroFilter

最终, 通过 ShiroFilterFactoryBean 的 createInstance: new SpringShiroFilter((WebSecurityManager) securityManager, chainResolver) securityManager 和 配置了 FilterChainManagerFilterCharinResolver “联系” 在了一起.
最后我们再来看看 ShiroFilter:
在这里插入图片描述

public abstract class AbstractShiroFilter extends OncePerRequestFilter {
    ...
    protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain)
            throws ServletException, IOException {
        Throwable t = null;

        try {
            final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);
            final ServletResponse response = prepareServletResponse(request, servletResponse, chain);

            final Subject subject = createSubject(request, response);

            subject.execute(new Callable() {
                public Object call() throws Exception {
                    updateSessionLastAccessTime(request, response);
                    executeChain(request, response, chain);
                    return null;
                }
            });
        } catch (ExecutionException ex) {
            t = ex.getCause();
        } catch (Throwable throwable) {
            t = throwable;
        }
        ...
    }    
    ...
    protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain)
            throws IOException, ServletException {
        FilterChain chain = getExecutionChain(request, response, origChain);
        chain.doFilter(request, response);
    }
    ...
    protected FilterChain getExecutionChain(ServletRequest request, ServletResponse response, FilterChain origChain) {
        FilterChain chain = origChain;

        FilterChainResolver resolver = getFilterChainResolver();
        if (resolver == null) {
            return origChain;
        }

        FilterChain resolved = resolver.getChain(request, response, origChain);
        if (resolved != null) {
            chain = resolved;
        } else {
            ...
        }

        return chain;
    }
}

可以看到在 ShiroFilter 的核心过滤方法中, 整个流程职级上就是通过 FilterChainResolver 获取到匹配的 FilterChain 然后 doFilter. 在 FilterChainResolver 的 getChain 中, 也主要是根据 requestURI 去获取匹配的过滤器链:

public class PathMatchingFilterChainResolver implements FilterChainResolver {
    ...
    public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) {
        FilterChainManager filterChainManager = getFilterChainManager();
        if (!filterChainManager.hasChains()) {
            return null;
        }

        final String requestURI = getPathWithinApplication(request);
        final String requestURINoTrailingSlash = removeTrailingSlash(requestURI);

        //the 'chain names' in this implementation are actually path patterns defined by the user.  We just use them
        //as the chain name for the FilterChainManager's requirements
        for (String pathPattern : filterChainManager.getChainNames()) {
            // If the path does match, then pass on to the subclass implementation for specific checks:
            if (pathMatches(pathPattern, requestURI)) {
                ...
                return filterChainManager.proxy(originalChain, pathPattern);
            } else {
                // in spring web, the requestURI "/resource/menus" ---- "resource/menus/" bose can access the resource
                // but the pathPattern match "/resource/menus" can not match "resource/menus/"
                // user can use requestURI + "/" to simply bypassed chain filter, to bypassed shiro protect

                pathPattern = removeTrailingSlash(pathPattern);

                if (pathMatches(pathPattern, requestURINoTrailingSlash)) {
                    ...
                    return filterChainManager.proxy(originalChain, requestURINoTrailingSlash);
                }
            }
        }

        return null;
    } 
    ...
}

综上, 这就是 ShiroFilter 的主要内容和源码摘录.

PART#3: Realm

还记得我们在定义 AuthRealm 时, 继承了 AuthorizingRealm 抽象类, 并实现了两个抽象方法:
​ ⑴ AuthorizingRealm`protected abstract AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals)`

​ ⑵ AuthenticatingRealm`protected abstract AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException`

可以对应下图看其中的关系:
在这里插入图片描述
还有一个问题: DefaultWebSecurityManagerRealm 是如何工作的?

@Configuration
public class ShiroConfiguration {
    ...
	@Bean("defaultWebSecurityManager")
    public DefaultWebSecurityManager defaultWebSecurityManager(@Qualifier("authRealm") Realm authRealm) {
        final DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        defaultWebSecurityManager.setRealm(authRealm);
        return defaultWebSecurityManager;
    }
    ...
}

↑ 在 ShiroConfiguration, 由开发者定义的 Realm 被设值到了 DefaultWebSecurityManager (通过 DefaultWebSecurityManager#setRealm(Realm)), 所以, 我们从 DefaultWebSecurityManager 开始 ↓

diagram-hierarchy-DefaultWebSecurityManager

通过跟踪源码可以简单归纳下 Realm 的调用链:

# 关键层级结构
DefaultWebSecurityManager
 extends AuthorizingSecurityManager
  extends AuthenticationSecurityManager
   extends RealmSecurityManager
    extends CachingSecurityManager
     implements SecurityManager

从调用链可以看出 DefaulutWebSecurityManager#setRealm 实际上是调用超类的了 RealmSecurityManager 的 setRealm 方法, 在方法体最后会执行 afterRealmSet 做一些补充设定, 关键就是在这个 afterRealmSet, 由于 “静态类型” 是 DefaultWebSecurityManager, 所以实际上会:

  1. 优先调用 AuthorizingSecurityManager 的 afterRealmSet (从下往上, 实际上是 “动态分配” 的过程),
  2. 然后是 AuthenticatingSecurityManager 的 afterRealmSet (通过 AuthorizingSecurityManager 的 afterRealmSet 方法体中的 super.afterRealmsSet()),
  3. 最后才是 RealmSecurityManager 的 afterRealmSet:
public abstract class RealmSecurityManager extends CachingSecurityManager {
    ...
    public void setRealm(Realm realm) {
        ...
        Collection<Realm> realms = new ArrayList<Realm>(1);
        realms.add(realm);
        setRealms(realms);
    }

    public void setRealms(Collection<Realm> realms) {
        ...
        this.realms = realms;
        afterRealmsSet();
    }
    ...
}

AuthorizingSecurityManager & Authorizer

AuthorizingSecurityManager 是 Shiro 提供的 SecurityManager 的实现, 负责将所有的授权操作 “委派” 给一个 Authorizer 的实例 (ModularRealmAuthorizer).
在这里插入图片描述
好了, 来看看 AuthorizingSecurityManager 的 afterRealmSet…

public abstract class AuthorizingSecurityManager extends AuthenticatingSecurityManager {
    ...
    public AuthorizingSecurityManager() {
        super();
        this.authorizer = new ModularRealmAuthorizer();
    }
	...
    protected void afterRealmsSet() {
        super.afterRealmsSet();
        if (this.authorizer instanceof ModularRealmAuthorizer) {
            ((ModularRealmAuthorizer) this.authorizer).setRealms(getRealms());
        }
    }
    ...
}

↑ 在 AuthorizingSecurityManager#afterRealmSet, 我们看到 AuthorizingSecurityManager 持有了一个名为 authorizer 的 ModularRealmAuthorizer, 透过 ModularRealmAuthorizer 的层次结构我们第一次认识了 Authorizer:
在这里插入图片描述
开发者设置的 Realm (在本文中, 也就是 AuthRealm) 被置入到了 ModularRealmAuthorizer 的 realms 属性中, 由名称可以推断, 这是一个 “授权器”. 这个授权器实现了接口 Authorizer, 后者定义了 Shiro 执行框架授权操作的规范:

org.apache.shiro.authz.Authorizer
org.apache.shiro.authz.Authorizer#isPermitted(PrincipalCollection, String)
org.apache.shiro.authz.Authorizer#isPermitted(PrincipalCollection, Permission)
org.apache.shiro.authz.Authorizer#isPermitted(PrincipalCollection, String...)
org.apache.shiro.authz.Authorizer#isPermitted(PrincipalCollection, List<Permission>)
org.apache.shiro.authz.Authorizer#isPermittedAll(PrincipalCollection, String...)
org.apache.shiro.authz.Authorizer#isPermittedAll(PrincipalCollection, Collection<Permission>)
org.apache.shiro.authz.Authorizer#checkPermission(PrincipalCollection, String)
org.apache.shiro.authz.Authorizer#checkPermission(PrincipalCollection, Permission)
org.apache.shiro.authz.Authorizer#checkPermissions(PrincipalCollection, String...)
org.apache.shiro.authz.Authorizer#checkPermissions(PrincipalCollection, Collection<Permission>)
org.apache.shiro.authz.Authorizer#hasRole
org.apache.shiro.authz.Authorizer#hasRoles
org.apache.shiro.authz.Authorizer#hasAllRoles
org.apache.shiro.authz.Authorizer#checkRole
org.apache.shiro.authz.Authorizer#checkRoles(PrincipalCollection, Collection<String>)
org.apache.shiro.authz.Authorizer#checkRoles(PrincipalCollection, String...)

↑ 这个 Principal 参数是能唯一标识一个用户的标记, 如数据库主键, 用户名等. 而这个 Principal 在运行期间的值是通过开发者配置的 Realm 获取的.

ModularRealmAuthorizersetRealms 方法中, 可以看到 applyPermissionResolverToRealms()applyRolePermissionResolverToRealms() 两个调用, 其中:

  • applyPermissionResolverToRealms() 会把 ModularRealmAuthorizer 持有的 permissionResolver 设置给 Realms (如果当前 Realm 实现了 PermissionResolverAware 接口). 如果没有指定, 就会使用 Realm 默认的解析器, 对于 AuthorizingRealm 来说, 就是 WildcardPermissionResolver
  • applyRolePermissionResolverToRealms() 会把 ModularRealmAuthorizer 持有的 rolePermissionResolver 设置给 Realms (如果当前 Realm 实现了 RolePermissionResolverAware 接口)
public class ModularRealmAuthorizer implements Authorizer, PermissionResolverAware, RolePermissionResolverAware {
    ...
    protected Collection<Realm> realms;
    protected PermissionResolver permissionResolver;
    protected RolePermissionResolver rolePermissionResolver;
	...
    public void setRealms(Collection<Realm> realms) {
        this.realms = realms;
        applyPermissionResolverToRealms();
        applyRolePermissionResolverToRealms();
    }
	...
    /**
     * Sets the internal getPermissionResolver on any internal configured Realms that implement the PermissionResolverAware interface.
     * This method is called after setting a permissionResolver on this ModularRealmAuthorizer via the setPermissionResolver method.
     * It is also called after setting one or more realms via the setRealms method to allow these newly available realms to be given the PermissionResolver already in use.
     */
    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);
                }
            }
        }
    }
    ...
    /**
     * Sets the internal getRolePermissionResolver on any internal configured Realms that implement the RolePermissionResolverAware interface.
     * This method is called after setting a rolePermissionResolver on this ModularRealmAuthorizer via the setRolePermissionResolver method.
     * It is also called after setting one or more realms via the setRealms method to allow these newly available realms to be given the RolePermissionResolver already in use.
     */
    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);
                }
            }
        }
    }
    ...
}

↑ 从上面可以看出, ModularRealmAuthorizer 实际上也是调用的 realm 的实现于 Authorizer 的授权方法. 而透过本节开头的层级图我们也可以看到 AuthRealm 的父类 AuthorizingRealm 也实现了 Authorizer 接口, 自然也继承了关于授权的 API.
最后一个问题, 授权操作是如何完成的? 我们接着看 AuthorizingRealm:

public abstract class AuthorizingRealm extends AuthenticatingRealm
        implements Authorizer, Initializable, PermissionResolverAware, RolePermissionResolverAware {
    ...
    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;
        }
    }
    ...
    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;
    }
    protected AuthorizationInfo getAuthorizationInfo(PrincipalCollection principals) {
        ...
        AuthorizationInfo info = null;
        ...
        Cache<Object, AuthorizationInfo> cache = getAvailableAuthorizationCache();
        if (cache != null) {
            ...
            Object key = getAuthorizationCacheKey(principals);
            info = cache.get(key);
            ...
        }

        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) {
                ...
                Object key = getAuthorizationCacheKey(principals);
                cache.put(key, info);
            }
        }

        return info;
    }
    protected 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);
        }
    }
}

上面这段代码摘录简要显示了 Shiro 如何从 realm 中获取授权信息并执行授权验证: 先用默认的 permissionResolver (WildcardPermissionResolver) 解析出 Permission (通过 PermissionResolver), 再调用 realm 的 doGetAuthorizationInfo 获取 AuthorizationInfo 从其中获取 Subject 的权限信息 (借助 RolePermissionResolver) 最后判断目标资源是否在 realm 的权限范围内 (org.apache.shiro.authz.Permission#implies).

其中, doGetAuthorizationInfo 正式我们在 AuthRealm 中实现的抽象方法.


总结一下, AuthorizingSecurityManager 提供了授权的支持, 该支持是通过将授权操作委派给 ModularRealmAuthorizer 来完成的 (AuthorizingSecurityManager 实现了 Authorizer 接口, 其对于接口的方法的实现内部本质上是调用了同样实现了 Authorizer 接口的 ModularRealmAuthorizer 的方法), 如:

public boolean isPermitted(PrincipalCollection principals, String permissionString) {
    return this.authorizer.isPermitted(principals, permissionString);
}

ModularRealmAuthorizer 持有 realms, permissionResolver 以及 rolePermissionResolver, 在 realms 被设置后会被置入 permissionResolver (如果当前 realm 实现了 PermissionResolverAware) 和 rolePermissionResolver (如果当前 realm 实现了 RolePermissionResolverAware).

同时由于 ModularRealmAuthorizer 实现了 Authorizer 接口, 它其中也实现了关于授权的全部 API, 但是其方法内部的实现是轮询持有的 realms, 对于实现了 Authorizer 接口的 realm 调用授权的 API, 如:

public boolean isPermitted(PrincipalCollection principals, Permission permission) {
    assertRealmsConfigured();
    for (Realm realm : getRealms()) {
        if (!(realm instanceof Authorizer)) continue;
        if (((Authorizer) realm).isPermitted(principals, permission)) {
            return true;
        }
    }
    return false;
}

AuthenticatingSecurityManager & Authenticator

看完了授权的操作, 我们再来看看认证逻辑是如何进行的…
AuthorizingSecurityManager 同样的, AuthenticationSecurityManager 也委派了 Authenticator (ModularRealmAuthenticator) 用于执行认证操作.

public abstract class AuthenticatingSecurityManager extends RealmSecurityManager {
    ...
    public AuthenticatingSecurityManager() {
        super();
        this.authenticator = new ModularRealmAuthenticator();
    }
	...
    protected void afterRealmsSet() {
        super.afterRealmsSet();
        if (this.authenticator instanceof ModularRealmAuthenticator) {
            ((ModularRealmAuthenticator) this.authenticator).setRealms(getRealms());
        }
    }
    ...
    public AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
    	return this.authenticator.authenticate(token);
	}	
}

↑ 这个 authenticator 默认实际是 ModularRealmAuthenticator, AuthrnticationSecurityManager 也同样地, 把 realms 设置给了 ModularRealmAuthenticator. 而这个实际的认证调用 authenticate, 通过源码我们可以看到是调用了 AbstractAuthenticator 的方法. ↓
在这里插入图片描述
来看看 AbstractAuthenticator 的 authenticate 究竟做了什么:

public abstract class AbstractAuthenticator implements Authenticator, LogoutAware {
    ...
	public final AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
		...
        AuthenticationInfo info;
        try {
            info = doAuthenticate(token);
            if (info == null) {
                String msg = "No account information found for authentication token [" + token + "] by this " +
                        "Authenticator instance.  Please check that it is configured correctly.";
                throw new AuthenticationException(msg);
            }
        } catch (Throwable t) {
            ...
            try {
                notifyFailure(token, ae);
            } catch (Throwable t2) {
                ...
            }
            throw ae;
        }
		...
        notifySuccess(token, info);
        return info;
    }
}

↑ 可以看到真正的认证调用实际上是调用了 AbstractAuthenticator 的抽象方法 doAuthenticator, 而该方法在 ModularRealmAuthenticator 实现:

public class ModularRealmAuthenticator extends AbstractAuthenticator {
    protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
        assertRealmsConfigured();
        Collection<Realm> realms = getRealms();
        if (realms.size() == 1) {
            return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
        } else {
            return doMultiRealmAuthentication(realms, authenticationToken);
        }
    }
    protected AuthenticationInfo doSingleRealmAuthentication(Realm realm, AuthenticationToken token) {
        ...
        AuthenticationInfo info = realm.getAuthenticationInfo(token);
        ...
        return info;
    }
    protected AuthenticationInfo doMultiRealmAuthentication(Collection<Realm> realms, AuthenticationToken token) {
        AuthenticationStrategy strategy = getAuthenticationStrategy();
        AuthenticationInfo aggregate = strategy.beforeAllAttempts(realms, token);
        ...
            for (Realm realm : realms) {
                try {
                    aggregate = strategy.beforeAttempt(realm, token, aggregate);
                } catch (ShortCircuitIterationException shortCircuitSignal) {
                    // Break from continuing with subsequnet realms on receiving 
                    // short circuit signal from strategy
                    break;
                }

                if (realm.supports(token)) {
                    ...
                        AuthenticationInfo info = null;
                    Throwable t = null;
                    try {
                        info = realm.getAuthenticationInfo(token);
                    } catch (Throwable throwable) {
                        ...
                    }
                    aggregate = strategy.afterAttempt(realm, token, info, aggregate, t);
                } else {
                    log.debug("Realm [{}] does not support token {}.  Skipping realm.", realm, token);
                }
            }
        aggregate = strategy.afterAllAttempts(token, aggregate);
        return aggregate;
    }

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

↑ 从上面源码我们可以得出, AuthentticatingSecurityManager 委派 ModularRealmAuthenticator 执行认证, 同时也把 realms 传入 ModularRealmAuthenticator. 而 ModularRealmAuthenticator 内部, 调用 realm 的 doGetAuthenticationInfo 获取 AuthenticationInfo, 并最终在 ModularRealmAuthenticator 的超类 AbstractAuthenticator 的 authenticate 中, 完成认证.

Conclusion

至此, 本文介绍了 Shiro 的认证和授权逻辑. 现在我们运行 demo-shiro-quickstart 项目应该可以成功访问到唯一的一个接口. 但是也可以看到 AuthRealm 中的两个实现方法都没被调用.
下一篇, 我们将首先介绍如何触发 AuthRealm 的 doGetAuthorizationInfo.

关于 Shiro 对 Subject 的管理以及其工作机制, 留到后面的文章说明…

Reference

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值