shiro源码解析

一、web.xml 文件中配置的 DelegatingFilterProxy 的 <filter-name>为啥与Spring文件中配置的ShiroFilterFactoryBean的Bean id 保持一致?

<filter>
        <filter-name>shiroFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
            <param-name>targetFilterLifecycle</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>

    <filter-mapping>
        <filter-name>shiroFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager"/>
        <property name="loginUrl" value="/login"/>
        <property name="successUrl" value="/list"/>
        <property name="unauthorizedUrl" value="/unauthorized"/>
        <!--  <property name="filterChainDefinitionMap" ref="filterChainDefinitionMap"></property>-->
        <!-- The 'filters' property is not necessary since any declared javax.servlet.Filter bean
             defined will be automatically acquired and available via its beanName in chain
             definitions, but you can perform overrides or parent/child consolidated configuration
             here if you like: -->
        <!-- <property name="filters">
            <util:map>
                <entry key="aName" value-ref="someFilterPojo"/>
            </util:map>
        </property> -->
        <property name="filterChainDefinitions">
            <value>
                /**/login = anon 
                /toLogin = anon
                # everything else requires authentication:
                /admin =roles[admin]
                /** = authc
            </value>
        </property>
    </bean>

 

1)Tomcat StandardContext中ContextLoaderListener初始化IOC容器,加载ShiroFilterFactoryBean。

 1 public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
 2         if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
 3             throw new IllegalStateException(
 4                     "Cannot initialize context because there is already a root application context present - " +
 5                     "check whether you have multiple ContextLoader* definitions in your web.xml!");
 6         }
 7 
 8         Log logger = LogFactory.getLog(ContextLoader.class);
 9         servletContext.log("Initializing Spring root WebApplicationContext");
10         if (logger.isInfoEnabled()) {
11             logger.info("Root WebApplicationContext: initialization started");
12         }
13         long startTime = System.currentTimeMillis();
14 
15         try {
16             // Store context in local instance variable, to guarantee that
17             // it is available on ServletContext shutdown.
18             if (this.context == null) {
19                 this.context = createWebApplicationContext(servletContext);
20             }
21             if (this.context instanceof ConfigurableWebApplicationContext) {
22                 ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
23                 if (!cwac.isActive()) {
24                     // The context has not yet been refreshed -> provide services such as
25                     // setting the parent context, setting the application context id, etc
26                     if (cwac.getParent() == null) {
27                         // The context instance was injected without an explicit parent ->
28                         // determine parent for root web application context, if any.
29                         ApplicationContext parent = loadParentContext(servletContext);
30                         cwac.setParent(parent);
31                     }
32                     configureAndRefreshWebApplicationContext(cwac, servletContext);
33                 }
34             }
35             servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
36 
37             ClassLoader ccl = Thread.currentThread().getContextClassLoader();
38             if (ccl == ContextLoader.class.getClassLoader()) {
39                 currentContext = this.context;
40             }
41             else if (ccl != null) {
42                 currentContextPerThread.put(ccl, this.context);
43             }
44 
45             if (logger.isDebugEnabled()) {
46                 logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
47                         WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
48             }
49             if (logger.isInfoEnabled()) {
50                 long elapsedTime = System.currentTimeMillis() - startTime;
51                 logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
52             }
53 
54             return this.context;
55         }
56         catch (RuntimeException ex) {
57             logger.error("Context initialization failed", ex);
58             servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
59             throw ex;
60         }
61         catch (Error err) {
62             logger.error("Context initialization failed", err);
63             servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
64             throw err;
65         }
66     }

19行,创建IOC容器对象。

32行,初始化IOC,加载bean到容器中。

2)StandardContext中Filter初始化。DelegatingFilterProxy的initFilterBean中从IOC容器中获取名为filter-name的bean

 1 protected void initFilterBean() throws ServletException {
 2         synchronized (this.delegateMonitor) {
 3             if (this.delegate == null) {
 4                 // If no target bean name specified, use filter name.
 5                 if (this.targetBeanName == null) {
 6                     this.targetBeanName = getFilterName();
 7                 }
 8                 // Fetch Spring root application context and initialize the delegate early,
 9                 // if possible. If the root application context will be started after this
10                 // filter proxy, we'll have to resort to lazy initialization.
11                 WebApplicationContext wac = findWebApplicationContext();
12                 if (wac != null) {
13                     this.delegate = initDelegate(wac);
14                 }
15             }
16         }
17     }
18 
19 protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
20         Filter delegate = wac.getBean(getTargetBeanName(), Filter.class);
21         if (isTargetFilterLifecycle()) {
22             delegate.init(getFilterConfig());
23         }
24         return delegate;
25     }

第20行从容器中获取name='shiroFilter'的bean,ShiroFilterFactoryBean实现了FactoryBean接口,容器获取对象时,返回ShiroFilterFactoryBean.getObject(),即返回SpringShiroFilter。

所以DelegatingFilterProxy的代理对象是SpringShiroFilter。

 1 public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
 2 protected <T> T doGetBean(
 3             final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
 4             throws BeansException {
 5 
 6         final String beanName = transformedBeanName(name);
 7         Object bean;
 8 
 9         // Eagerly check singleton cache for manually registered singletons.
10         Object sharedInstance = getSingleton(beanName);
11         if (sharedInstance != null && args == null) {
12             if (logger.isDebugEnabled()) {
13                 if (isSingletonCurrentlyInCreation(beanName)) {
14                     logger.debug("Returning eagerly cached instance of singleton bean '" + beanName +
15                             "' that is not fully initialized yet - a consequence of a circular reference");
16                 }
17                 else {
18                     logger.debug("Returning cached instance of singleton bean '" + beanName + "'");
19                 }
20             }
21             bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
22         }
23     ......
24   }
25 }

第10行,从容器中返回ShiroFilterFactoryBean对象。

第21行,执行ShiroFilterFactoryBean.getObject(),返回SpringShiroFilter对象。

 

二、ShiroFilterFactoryBean中filterChainDefinitions属性,配置了url需要拦截验证的Filter,shiro中配置的filter是怎样工作的?

<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager"/>
        <property name="loginUrl" value="/login"/>
        <property name="successUrl" value="/list"/>
        <property name="unauthorizedUrl" value="/unauthorized"/>
        <property name="filterChainDefinitions">
            <value>
                /**/login = anon 
                /toLogin = anon
                # everything else requires authentication:
                /admin =roles[admin]
                /** = authc
            </value>
        </property>
    </bean>

1)首先分析下filterChainDefinitions中配置url规则在ShiroFilterFactoryBean怎样初始化的。

 1 protected AbstractShiroFilter createInstance() throws Exception {
 2 
 3         log.debug("Creating Shiro Filter instance.");
 4 
 5         SecurityManager securityManager = getSecurityManager();
 6         if (securityManager == null) {
 7             String msg = "SecurityManager property must be set.";
 8             throw new BeanInitializationException(msg);
 9         }
10 
11         if (!(securityManager instanceof WebSecurityManager)) {
12             String msg = "The security manager does not implement the WebSecurityManager interface.";
13             throw new BeanInitializationException(msg);
14         }
15 
16         FilterChainManager manager = createFilterChainManager();
17 
18         //Expose the constructed FilterChainManager by first wrapping it in a
19         // FilterChainResolver implementation. The AbstractShiroFilter implementations
20         // do not know about FilterChainManagers - only resolvers:
21         PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();
22         chainResolver.setFilterChainManager(manager);
23 
24         //Now create a concrete ShiroFilter instance and apply the acquired SecurityManager and built
25         //FilterChainResolver.  It doesn't matter that the instance is an anonymous inner class
26         //here - we're just using it because it is a concrete AbstractShiroFilter instance that accepts
27         //injection of the SecurityManager and FilterChainResolver:
28         return new SpringShiroFilter((WebSecurityManager) securityManager, chainResolver);
29     }

从这里可以知道,首先获取filterChainManager,具体方法如下

 1 protected FilterChainManager createFilterChainManager() {
 2 
 3         DefaultFilterChainManager manager = new DefaultFilterChainManager();
 4         Map<String, Filter> defaultFilters = manager.getFilters();
 5         //apply global settings if necessary:
 6         for (Filter filter : defaultFilters.values()) {
 7             applyGlobalPropertiesIfNecessary(filter);
 8         }
 9 
10         //Apply the acquired and/or configured filters:
11         Map<String, Filter> filters = getFilters();
12         if (!CollectionUtils.isEmpty(filters)) {
13             for (Map.Entry<String, Filter> entry : filters.entrySet()) {
14                 String name = entry.getKey();
15                 Filter filter = entry.getValue();
16                 applyGlobalPropertiesIfNecessary(filter);
17                 if (filter instanceof Nameable) {
18                     ((Nameable) filter).setName(name);
19                 }
20                 //'init' argument is false, since Spring-configured filters should be initialized
21                 //in Spring (i.e. 'init-method=blah') or implement InitializingBean:
22                 manager.addFilter(name, filter, false);
23             }
24         }
25 
26         //build up the chains:
27         Map<String, String> chains = getFilterChainDefinitionMap();
28         if (!CollectionUtils.isEmpty(chains)) {
29             for (Map.Entry<String, String> entry : chains.entrySet()) {
30                 String url = entry.getKey();
31                 String chainDefinition = entry.getValue();
32                 manager.createChain(url, chainDefinition);
33             }
34         }
35 
36         return manager;
37     }

第3行根据DefaultFilter中枚举定义的shiro默认的filter映射加入到DefaultFilterChainManager.filters。

第29行在DefaultFilterChainManager中还做了一件事就是url-filter的映射变成filterChain,这句代码就是执行这个任务(将我们在xml文件中定义的filterChainDefinitions变成filterChain)。

 1 public void createChain(String chainName, String chainDefinition) {
 2         if (!StringUtils.hasText(chainName)) {
 3             throw new NullPointerException("chainName cannot be null or empty.");
 4         }
 5         if (!StringUtils.hasText(chainDefinition)) {
 6             throw new NullPointerException("chainDefinition cannot be null or empty.");
 7         }
 8 
 9         if (log.isDebugEnabled()) {
10             log.debug("Creating chain [" + chainName + "] from String definition [" + chainDefinition + "]");
11         }
12 
13         //parse the value by tokenizing it to get the resulting filter-specific config entries
14         //
15         //e.g. for a value of
16         //
17         //     "authc, roles[admin,user], perms[file:edit]"
18         //
19         // the resulting token array would equal
20         //
21         //     { "authc", "roles[admin,user]", "perms[file:edit]" }
22         //
23         String[] filterTokens = splitChainDefinition(chainDefinition);
24 
25         //each token is specific to each filter.
26         //strip the name and extract any filter-specific config between brackets [ ]
27         for (String token : filterTokens) {
28             String[] nameConfigPair = toNameConfigPair(token);
29 
30             //now we have the filter name, path and (possibly null) path-specific config.  Let's apply them:
31             addToChain(chainName, nameConfigPair[0], nameConfigPair[1]);
32         }
33     }

第23行作用是将权限分割:如

"authc, roles[admin,user], perms[file:edit]"

将会被分割为

{ "authc", "roles[admin,user]", "perms[file:edit]" }

第28行 通过toNameConfigPair(token)将如:roles[admin,user]形式的变成roles,admin,user形式的分割

第31行 将定义的url 及roles,admin,user封装到DefaultFilterChainManager.filterChains中

 1 public void addToChain(String chainName, String filterName, String chainSpecificFilterConfig) {
 2         if (!StringUtils.hasText(chainName)) {
 3             throw new IllegalArgumentException("chainName cannot be null or empty.");
 4         }
 5         Filter filter = getFilter(filterName);
 6         if (filter == null) {
 7             throw new IllegalArgumentException("There is no filter with name '" + filterName +
 8                     "' to apply to chain [" + chainName + "] in the pool of available Filters.  Ensure a " +
 9                     "filter with that name/path has first been registered with the addFilter method(s).");
10         }
11 
12         applyChainConfig(chainName, filter, chainSpecificFilterConfig);
13 
14         NamedFilterList chain = ensureChain(chainName);
15         chain.add(filter);
16     }

2)下面分析当url请求到来的时候,shiro是如何完成过滤的。

当url请求到来时执行DelegatingFilterProxy.doFilter(),由于代理的是SpringShiroFilter对象。

 1 public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
 2             throws ServletException, IOException {
 3 
 4         // Lazily initialize the delegate if necessary.
 5         Filter delegateToUse = this.delegate;
 6         if (delegateToUse == null) {
 7             synchronized (this.delegateMonitor) {
 8                 if (this.delegate == null) {
 9                     WebApplicationContext wac = findWebApplicationContext();
10                     if (wac == null) {
11                         throw new IllegalStateException("No WebApplicationContext found: no ContextLoaderListener registered?");
12                     }
13                     this.delegate = initDelegate(wac);
14                 }
15                 delegateToUse = this.delegate;
16             }
17         }
18 
19         // Let the delegate perform the actual doFilter operation.
20         invokeDelegate(delegateToUse, request, response, filterChain);
21     }

分析SpringShiroFilter.doFilter()。SpringShiroFilter继承了AbstractShiroFilter类,最终调用了AbstractShiroFilter.doFilterInternal()

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

            //noinspection unchecked
            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;
        }

        if (t != null) {
            if (t instanceof ServletException) {
                throw (ServletException) t;
            }
            if (t instanceof IOException) {
                throw (IOException) t;
            }
            //otherwise it's not one of the two exceptions expected by the filter method signature - wrap it in one:
            String msg = "Filtered request failed.";
            throw new ServletException(msg, t);
        }
    }

暂时不关心subject相关的创建等过程,只关心这行代码

executeChain(request, response, chain);

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) {
            log.debug("No FilterChainResolver configured.  Returning original FilterChain.");
            return origChain;
        }

        FilterChain resolved = resolver.getChain(request, response, origChain);
        if (resolved != null) {
            log.trace("Resolved a configured FilterChain for the current request.");
            chain = resolved;
        } else {
            log.trace("No FilterChain configured for the current request.  Using the default.");
        }

        return chain;
    }

这里用到了我们在创建SpringShiroFilter时传递的FilterChainResolver,至此,我们终于找到了getChain()方法在这里被调用了。其源码实现如下

 1 public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) {
 2         FilterChainManager filterChainManager = getFilterChainManager();
 3         if (!filterChainManager.hasChains()) {
 4             return null;
 5         }
 6 
 7         String requestURI = getPathWithinApplication(request);
 8 
 9         //the 'chain names' in this implementation are actually path patterns defined by the user.  We just use them
10         //as the chain name for the FilterChainManager's requirements
11         for (String pathPattern : filterChainManager.getChainNames()) {
12 
13             // If the path does match, then pass on to the subclass implementation for specific checks:
14             if (pathMatches(pathPattern, requestURI)) {
15                 if (log.isTraceEnabled()) {
16                     log.trace("Matched path pattern [" + pathPattern + "] for requestURI [" + requestURI + "].  " +
17                             "Utilizing corresponding filter chain...");
18                 }
19                 return filterChainManager.proxy(originalChain, pathPattern);
20             }
21         }
22 
23         return null;
24     }

从for循环可以看出,当匹配到第一个url规则,则return一个代表这个url规则的FilterChain给web容器执行。

FilterChain的实现类为org.apache.shiro.web.servlet.ProxiedFilterChain

public ProxiedFilterChain(FilterChain orig, List<Filter> filters) {
        if (orig == null) {
            throw new NullPointerException("original FilterChain cannot be null.");
        }
        this.orig = orig;// servlet中的FilterChai对象
        this.filters = filters;//根据本次访问url获取匹配的shiro filters
        this.index = 0;
    }

从该类的doFilter方法可以知道,它会将Filter链的Filter的doFilter方法顺序执行一遍。下图展示了这一过程

public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
        if (this.filters == null || this.filters.size() == this.index) {
            //we've reached the end of the wrapped chain, so invoke the original one:
            if (log.isTraceEnabled()) {
                log.trace("Invoking original filter chain.");
            }
            this.orig.doFilter(request, response);
        } else {
            if (log.isTraceEnabled()) {
                log.trace("Invoking wrapped filter at index [" + this.index + "]");
            }
            this.filters.get(this.index++).doFilter(request, response, this);
        }
    }

根据本次访问url获取匹配的shiro filters的doFilter方法都执行完成后,继续执行web.xml中定义的filter.doFilter方法

 

参考:https://www.cnblogs.com/ljdblog/p/6237683.html

posted @ 2019-03-03 18:56  小亮的BLOG  阅读(1077)  评论(0编辑  收藏  举报