1. 程式人生 > >shiro學習筆記:整合spring之攔截器鏈執行流程

shiro學習筆記:整合spring之攔截器鏈執行流程

一、環境準備

搭建好spring + shiro整合環境(本文環境Spring 4.3.10.RELEASE + Shiro 1.4.0)後,編寫登入頁面如下:
<html>
<head>
<title>登入頁</title>
</head>
<body>
<div style="color:red;">${shiroLoginFailure}</div>
	<form action="" method="post">
		使用者名稱:<input type="text" name="username"><br /> 
		密碼:<input type="password" name="password"><br /> 
		<input type="submit" value="登入">
	</form>
</body>
</html>
shiro攔截器部分配置:
    <!-- 基於Form表單的身份驗證過濾器 -->
    <bean id="formAuthenticationFilter" class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter">
        <property name="usernameParam" value="username"/>
        <property name="passwordParam" value="password"/>
        <property name="loginUrl" value="/login.jsp"/>
    </bean>
    
    <!-- Shiro的Web過濾器:此處使用ShiroFilterFactoryBean來建立ShiroFilter過濾器 -->
    <!-- Bean id必須和web.xml檔案中配置的DelegatingFilterProxy的filter-name一致 -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager"/>
        <property name="loginUrl" value="/login.jsp"/>
        <property name="unauthorizedUrl" value="/unauthorized.jsp"/>
        <property name="filters">
            <util:map>
                <entry key="authc" value-ref="formAuthenticationFilter"/>
            </util:map>
        </property>
        <property name="filterChainDefinitions">
            <value>
                /index.jsp = anon
                /unauthorized.jsp = anon
                /error.jsp = anon
                /login.jsp = authc
                /logout = logout
                /** = user
            </value>
        </property>
    </bean>
shiro攔截器樹狀結構如下圖:

其中幾個主要的攔截:OnceperRequestFilter、AbstractShiroFilter、AdviceFilter、PathMatchingFilter、AccessControlFilter、AuthenticationFilter、AuthorizationFilter等構成shiro的攔截器基本架構。

二、初始化過程

啟動專案時spring首先會通過doGetObjectFromFactoryBean()方法來初始化Shiro的攔截器入口工廠類,即org.apache.shiro.spring.web.ShiroFilterFactoryBean:
/**
 * Obtain an object to expose from the given FactoryBean.
 * @param factory the FactoryBean instance
 * @param beanName the name of the bean
 * @return the object obtained from the FactoryBean
 * @throws BeanCreationException if FactoryBean object creation failed
 * @see org.springframework.beans.factory.FactoryBean#getObject()
 */
private Object doGetObjectFromFactoryBean(final FactoryBean<?> factory, 
	final String beanName) throws BeanCreationException {
	Object object;
	try {
		if (System.getSecurityManager() != null) {
			AccessControlContext acc = getAccessControlContext();
			try {
				object = AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
					@Override
					public Object run() throws Exception {
						return factory.getObject();
					}
				}, acc);
			} catch (PrivilegedActionException pae) {
				throw pae.getException();
			}
		} else {
			// 呼叫工廠方法生成例項
			object = factory.getObject();
		}
	} catch (FactoryBeanNotInitializedException ex) {
		throw new BeanCurrentlyInCreationException(beanName, ex.toString());
	} catch (Throwable ex) {
		throw new BeanCreationException(beanName, "FactoryBean threw exception on object creation", ex);
	}
	// Do not accept a null value for a FactoryBean that's not fully
	// initialized yet: Many FactoryBeans just return null then.
	if (object == null && isSingletonCurrentlyInCreation(beanName)) {
		throw new BeanCurrentlyInCreationException(beanName, "FactoryBean which is currently in creation returned null from getObject");
	}
	return object;
}
ShiroFilterFactoryBean提供的獲取例項的工廠方法:
/**
 * Lazily creates and returns a {@link AbstractShiroFilter} concrete instance via the
 * {@link #createInstance} method.
 *
 * @return the application's Shiro Filter instance used to filter incoming web requests.
 * @throws Exception if there is a problem creating the {@code Filter} instance.
 */
public Object getObject() throws Exception {
    if (instance == null) {
        instance = createInstance();
    }
    return instance;
}
最終會通過呼叫ShiroFilterFactoryBean的createInstance()方法初始化shiro攔截器入口類:
protected AbstractShiroFilter createInstance() throws Exception {

    log.debug("Creating Shiro Filter instance.");
    // 獲取的是配置的DefaultWebSecurityManager例項
    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);
    }
    // 建立負責維護URL模式與攔截器鏈關係的DefaultFilterChainManager例項
    FilterChainManager manager = createFilterChainManager();

    //Expose the constructed FilterChainManager by first wrapping it in a
    // FilterChainResolver implementation. The AbstractShiroFilter implementations
    // do not know about FilterChainManagers - only resolvers:
    // 建立shiro提供的唯一FilterChainResolver實現,用於解析訪問路徑所對應的攔截器鏈
    PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();
    chainResolver.setFilterChainManager(manager);

    //Now create a concrete ShiroFilter instance and apply the acquired SecurityManager and built
    //FilterChainResolver.  It doesn't matter that the instance is an anonymous inner class
    //here - we're just using it because it is a concrete AbstractShiroFilter instance that accepts
    //injection of the SecurityManager and FilterChainResolver:
    // 最終返回的是SpringShiroFilter例項(ShiroFilterFactoryBean的內部類)
    return new SpringShiroFilter((WebSecurityManager) securityManager, chainResolver);
}
接下來看看shiro在createFilterChainManager()方法如何建立FilterChainManager例項:
protected FilterChainManager createFilterChainManager() {
    // 建立的是shiro預設的DefaultFilterChainManager例項(構造方法內初始化預設攔截器)
    DefaultFilterChainManager manager = new DefaultFilterChainManager();
    // 獲取shiro預設的攔截器Map<攔截路徑,攔截器>對映集合
    Map<String, Filter> defaultFilters = manager.getFilters();
    // Apply global settings if necessary:(為預設攔截器設定通用屬性例如loginUrl,unauthroizedUrl等)
    for (Filter filter : defaultFilters.values()) {
        applyGlobalPropertiesIfNecessary(filter);
    }
    // Apply the acquired and/or configured filters:獲取配置檔案中filters屬性配置的攔截器Map集合
    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:
            // 將自定義攔截器加入FilterChainManager中的攔截器Map<攔截器名,攔截器例項>
            manager.addFilter(name, filter, false);
        }
    }
    // build up the chains:獲取自定義攔截器鏈Map交由Manager管理(對應的配置檔案屬性是filterChainDefinitions)
    Map<String, String> chains = getFilterChainDefinitionMap();
    if (!CollectionUtils.isEmpty(chains)) {
        for (Map.Entry<String, String> entry : chains.entrySet()) {
            String url = entry.getKey();// 配置的URL
            String chainDefinition = entry.getValue();// 該URL對應的攔截器例項
            // 解析filterChainDefinitions配置:每一個配置的URL對應一個shiro代理攔截器鏈
            // 並將解析的每一個攔截器鏈交由DefaultFilterChainManager的Map<URL路徑,攔截器鏈>管理
            manager.createChain(url, chainDefinition);
        }
    }
    return manager;
}
shiro預設攔截器鏈管理器DefaultFilterChainManager的初始化構造方法:
// DefaultFilterChainManager在初始化時新增預設攔截器
public DefaultFilterChainManager() {
    // 此處初始化攔截器Map<攔截器名稱,攔截器例項>:這裡儲存所有的預設以及自定義的攔截器
    this.filters = new LinkedHashMap<String, Filter>();
    // 此處初始化攔截器鏈Map<攔截器鏈名稱(攔截路徑),攔截器集合>:這裡儲存所有自定義的攔截器鏈所對應的攔截器集合
    this.filterChains = new LinkedHashMap<String, NamedFilterList>();
    addDefaultFilters(false);
}

// DefaultFilter是一個列舉類,定義了shiro的所有預設攔截器
protected void addDefaultFilters(boolean init) {
    for (DefaultFilter defaultFilter : DefaultFilter.values()) {
    	// 將所有預設攔截器加入filters集合中
        addFilter(defaultFilter.name(), defaultFilter.newInstance(), init, false);
    }
}
啟動時解析攔截器鏈定義的set方法:
/**
 * A convenience method that sets the {@link #setFilterChainDefinitionMap(java.util.Map) filterChainDefinitionMap}
 * property by accepting a {@link java.util.Properties Properties}-compatible string (multi-line key/value pairs).
 * Each key/value pair must conform to the format defined by the
 * {@link FilterChainManager#createChain(String,String)} JavaDoc - each property key is an ant URL
 * path expression and the value is the comma-delimited chain definition.
 *
 * @param definitions a {@link java.util.Properties Properties}-compatible string (multi-line key/value pairs)
 *                    where each key/value pair represents a single urlPathExpression-commaDelimitedChainDefinition.
 */
// ShiroFilterFactoryBean的setFilterChainDefinitions方法:解析配置的"filterChainDefinitions"屬性
public void setFilterChainDefinitions(String definitions) {
    Ini ini = new Ini();
    ini.load(definitions);
    //did they explicitly state a 'urls' section?  Not necessary, but just in case:
    Ini.Section section = ini.getSection(IniFilterChainResolverFactory.URLS);
    if (CollectionUtils.isEmpty(section)) {
        //no urls section.  Since this _is_ a urls chain definition property, just assume the
        //default section contains only the definitions:
        section = ini.getSection(Ini.DEFAULT_SECTION_NAME);
    }
    // 最後解析為Map<攔截路徑,攔截器鏈定義>並呼叫set方法初始化
    setFilterChainDefinitionMap(section);
}

/**
 * Sets the chainName-to-chainDefinition map of chain definitions to use for creating filter chains intercepted
 * by the Shiro Filter.  Each map entry should conform to the format defined by the
 * {@link FilterChainManager#createChain(String, String)} JavaDoc, where the map key is the chain name (e.g. URL
 * path expression) and the map value is the comma-delimited string chain definition.
 *
 * @param filterChainDefinitionMap the chainName-to-chainDefinition map of chain definitions to use for creating
 *                                 filter chains intercepted by the Shiro Filter.
 */
// ShiroFilterFactoryBean的set方法:初始化攔截器定義Map
public void setFilterChainDefinitionMap(Map<String, String> filterChainDefinitionMap) {
    this.filterChainDefinitionMap = filterChainDefinitionMap;
}
Shiro宣告的預設攔截器列舉類:
public enum DefaultFilter {
    anon(AnonymousFilter.class),
    authc(FormAuthenticationFilter.class),
    authcBasic(BasicHttpAuthenticationFilter.class),
    logout(LogoutFilter.class),
    noSessionCreation(NoSessionCreationFilter.class),
    perms(PermissionsAuthorizationFilter.class),
    port(PortFilter.class),
    rest(HttpMethodPermissionFilter.class),
    roles(RolesAuthorizationFilter.class),
    ssl(SslFilter.class),
    user(UserFilter.class);

    private final Class<? extends Filter> filterClass;

    private DefaultFilter(Class<? extends Filter> filterClass) {
        this.filterClass = filterClass;
    }

    public Filter newInstance() {
        return (Filter) ClassUtils.newInstance(this.filterClass);
    }

    public Class<? extends Filter> getFilterClass() {
        return this.filterClass;
    }

    public static Map<String, Filter> createInstanceMap(FilterConfig config) {
        Map<String, Filter> filters = new LinkedHashMap<String, Filter>(values().length);
        for (DefaultFilter defaultFilter : values()) {
            Filter filter = defaultFilter.newInstance();
            if (config != null) {
                try {
                    filter.init(config);
                } catch (ServletException e) {
                    String msg = "Unable to correctly init default filter instance of type " + filter.getClass().getName();
                    throw new IllegalStateException(msg, e);
                }
            }
            filters.put(defaultFilter.name(), filter);
        }
    return filters;
    }
}

再看Shiro如何通過DefaultFilterChainManager的createChain方法初始化攔截器鏈:

/**
 * @param chainName 攔截器鏈名稱,即攔截路徑
 * @param chainDefinition 攔截器鏈定義,即配置的以逗號分割的攔截器定義字串
 */
public void createChain(String chainName, String chainDefinition) {
    if (!StringUtils.hasText(chainName)) {
        throw new NullPointerException("chainName cannot be null or empty.");
    }
    if (!StringUtils.hasText(chainDefinition)) {
        throw new NullPointerException("chainDefinition cannot be null or empty.");
    }

    if (log.isDebugEnabled()) {
        log.debug("Creating chain [" + chainName + "] from String definition [" + chainDefinition + "]");
    }

    //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) {
        // 解析攔截器定義(例如authc[bar,baz])得到攔截器名稱及其引數的陣列即:array[0]="authc",array[1]="bar,baz"
        String[] nameConfigPair = toNameConfigPair(token);
        // now we have the filter name, path and (possibly null) path-specific config.  Let's apply them:
        // 根據攔截器鏈名稱(攔截URL),攔截器名稱,攔截器引數(可選),初始化攔截器鏈
        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.");
    }
    // 從之前初始化的Map攔截器集合中獲取攔截器例項
    Filter filter = getFilter(filterName);
    if (filter == null) {
        throw new IllegalArgumentException("There is no filter with name '" + filterName +
                "' to apply to chain [" + chainName + "] in the pool of available Filters.  Ensure a " +
                "filter with that name/path has first been registered with the addFilter method(s).");
    }
    // 應用當前攔截器引數配置
    applyChainConfig(chainName, filter, chainSpecificFilterConfig);
    // 確保攔截器鏈不為NULL,返回的是該攔截器鏈對應的攔截器List集合
    NamedFilterList chain = ensureChain(chainName);
    // 將當前攔截器加入攔截器List集合
    chain.add(filter);
}
Shiro通過DefaultFilterChainManager的applyChainConfig()方法設定各個攔截器的配置引數:
// DefaultFilterChainManager的applyChainConfig()方法
protected void applyChainConfig(String chainName, Filter filter, String chainSpecificFilterConfig) {
    if (log.isDebugEnabled()) {
        log.debug("Attempting to apply path [" + chainName + "] to filter [" + filter + "] " +
                "with config [" + chainSpecificFilterConfig + "]");
    }
    // 判斷當前攔截器是否是PathConfigProcessor型別
    // shiro預設的攔截器中除logout外均繼承至PathMatchingFilter,而PathMatchingFilter實現了介面PathConfigProcessor
    if (filter instanceof PathConfigProcessor) {
        // 呼叫當前攔截器例項的processPathConfig方法處理攔截路徑引數配置
        ((PathConfigProcessor) filter).processPathConfig(chainName, chainSpecificFilterConfig);
    } else {
        if (StringUtils.hasText(chainSpecificFilterConfig)) {
            // 如果攔截器不屬於PathConfigProcessor型別且配有引數則會丟擲異常
            //they specified a filter configuration, but the Filter doesn't implement PathConfigProcessor
            //this is an erroneous config:
            String msg = "chainSpecificFilterConfig was specified, but the underlying " +
                    "Filter instance is not an 'instanceof' " +
                    PathConfigProcessor.class.getName() + ".  This is required if the filter is to accept " +
                    "chain-specific configuration.";
            throw new ConfigurationException(msg);
        }
    }
}
執行攔截器自己的processPathConfig()方法:
/**
 * Splits any comma-delmited values that might be found in the <code>config</code> argument and sets the resulting
 * <code>String[]</code> array on the <code>appliedPaths</code> internal Map.
 * <p/>
 * That is:
 * <pre><code>
 * String[] values = null;
 * if (config != null) {
 *     values = split(config);
 * }
 * <p/>
 * this.{@link #appliedPaths appliedPaths}.put(path, values);
 * </code></pre>
 *
 * @param path   the application context path to match for executing this filter.
 * @param config the specified for <em>this particular filter only</em> for the given <code>path</code>
 * @return this configured filter.
 */
// PathMatchingFilter的processPathConfig方法(實現PathConfigProcessor介面的方法)
public Filter processPathConfig(String path, String config) {
    String[] values = null;
    if (config != null) {
    	// 按照逗號分隔符繼續分解配置引數
        values = split(config);
    }
    // 將<攔截路徑-配置引數>放入當前攔截器例項的Map集合(繼承至父類PathMatchingFilter的屬性)
    // 當以後觸發攔截器時即可根據攔截路徑獲取引數配置進行相關處理,如果沒有配置則values存null
    this.appliedPaths.put(path, values);
    return this;
}

/**
 * A collection of path-to-config entries where the key is a path which this filter should process and
 * the value is the (possibly null) configuration element specific to this Filter for that specific path.
 * <p/>
 * <p>To put it another way, the keys are the paths (urls) that this Filter will process.
 * <p>The values are filter-specific data that this Filter should use when processing the corresponding
 * key (path).  The values can be null if no Filter-specific config was specified for that url.
 */
// PathMatchingFilter的攔截路徑引數配置Map<攔截路徑,配置引數>集合
protected Map<String, Object> appliedPaths = new LinkedHashMap<String, Object>();
保險方法ensureChain()確保攔截器鏈不為空:
// DefaultFilterChainManager的ensureChain方法
protected NamedFilterList ensureChain(String chainName) {
    // 根據攔截器鏈名稱從攔截器鏈Map集合中獲取對應的攔截器集合
    NamedFilterList chain = getChain(chainName);
    if (chain == null) {
        // 如果不存在則進行初始化
        chain = new SimpleNamedFilterList(chainName);
        this.filterChains.put(chainName, chain);
    }
    return chain;
}

public NamedFilterList getChain(String chainName) {
    return this.filterChains.get(chainName);
}

三、攔截過程淺析

這裡首先以訪問工程路徑為例,即http://127.0.0.1:8082/shiro02。在debug模式下,首先進入到攔截器的doFilter()方法,實際上這個攔截器例項就是上面提到的createInstance()方法建立的SpringShiroFilter攔截器例項,這個類繼承至AbstractShiroFilter,是ShiroFilterFactoryBean的內部類,具體請參考上面的類結構圖。
/**
 * This {@code doFilter} implementation stores a request attribute for
 * "already filtered", proceeding without filtering again if the
 * attribute is already there.
 *
 * @see #getAlreadyFilteredAttributeName
 * @see #shouldNotFilter
 * @see #doFilterInternal
 */
// 當前進入的攔截方法是其父類OncePerRequestFilter的doFilter方法
public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
    // 獲取當前攔截器例項的標識名稱,用於標識其是否已經執行過
    String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
    // 判斷如果已經當前攔截器已經執行過則執行下一個攔截器
    if ( request.getAttribute(alreadyFilteredAttributeName) != null ) {
        log.trace("Filter '{}' already executed.  Proceeding without invoking this filter.", getName());
        filterChain.doFilter(request, response);
    // 判斷當前攔截器是否已經開啟(配置檔案可配,預設開啟) (shouldNotFilter()方法已廢棄返回值固定為false)
    } else //noinspection deprecation
        if (/* added in 1.2: */ !isEnabled(request, response) ||
            /* retain backwards compatibility: */ shouldNotFilter(request) ) {
        log.debug("Filter '{}' is not enabled for the current request.  Proceeding without invoking this filter.",
                getName());
        filterChain.doFilter(request, response);
    // 如果當前攔截器沒有執行且設定為開啟攔截,則進入攔截器方法
    } else {
        // Do invoke this filter...
        log.trace("Filter '{}' not yet executed.  Executing now.", getName());
        // 標識當前攔截器已經執行過
        request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
        try {
            // 執行過濾邏輯,這裡呼叫的是其父類AbstractShiroFilter的方法
            doFilterInternal(request, response, filterChain);
        } finally {
            // Once the request has finished, we're done and we don't need to mark as 'already filtered' any more.
            request.removeAttribute(alreadyFilteredAttributeName);
        }
    }
}
下面進入doFilterInternal()方法:
/**
 * {@code doFilterInternal} implementation that sets-up, executes, and cleans-up a Shiro-filtered request.  It
 * performs the following ordered operations:
 * <ol>
 * <li>{@link #prepareServletRequest(ServletRequest, ServletResponse, FilterChain) Prepares}
 * the incoming {@code ServletRequest} for use during Shiro's processing</li>
 * <li>{@link #prepareServletResponse(ServletRequest, ServletResponse, FilterChain) Prepares}
 * the outgoing {@code ServletResponse} for use during Shiro's processing</li>
 * <li> {@link #createSubject(javax.servlet.ServletRequest, javax.servlet.ServletResponse) Creates} a
 * {@link Subject} instance based on the specified request/response pair.</li>
 * <li>Finally {@link Subject#execute(Runnable) executes} the
 * {@link #updateSessionLastAccessTime(javax.servlet.ServletRequest, javax.servlet.ServletResponse)} and
 * {@link #executeChain(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)}
 * methods</li>
 * </ol>
 * <p/>
 * The {@code Subject.}{@link Subject#execute(Runnable) execute(Runnable)} call in step #4 is used as an
 * implementation technique to guarantee proper thread binding and restoration is completed successfully.
 *
 * @param servletRequest  the incoming {@code ServletRequest}
 * @param servletResponse the outgoing {@code ServletResponse}
 * @param chain           the container-provided {@code FilterChain} to execute
 * @throws IOException                    if an IO error occurs
 * @throws javax.servlet.ServletException if an Throwable other than an IOException
 */
// 當前為SpringShiroFilter攔截器的父類AbstractShiroFilter的doFilterInternal方法
protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain)
        throws ServletException, IOException {
    Throwable t = null;
    try {
        // Shiro在此對HttpServletRequest、HttpServletResponse進行封裝
        final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);
        final ServletResponse response = prepareServletResponse(request, servletResponse, chain);
        // 建立subject例項
        final Subject subject = createSubject(request, response);
        // noinspection unchecked
        // 執行回撥,最終執行的是下面的call()方法
        subject.execute(new Callable() {
            public Object call() throws Exception {
                // 更新session最後活動時間
                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);
    }
}
/**
 * Executes a {@link FilterChain} for the given request.
 * <p/>
 * This implementation first delegates to
 * <code>{@link #getExecutionChain(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) getExecutionChain}</code>
 * to allow the application's Shiro configuration to determine exactly how the chain should execute.  The resulting
 * value from that call is then executed directly by calling the returned {@code FilterChain}'s
 * {@link FilterChain#doFilter doFilter} method.  That is:
 * <pre>
 * FilterChain chain = {@link #getExecutionChain}(request, response, origChain);
 * chain.{@link FilterChain#doFilter doFilter}(request,response);</pre>
 *
 * @param request   the incoming ServletRequest
 * @param response  the outgoing ServletResponse
 * @param origChain the Servlet Container-provided chain that may be wrapped further by an application-configured
 *                  chain of Filters.
 * @throws IOException      if the underlying {@code chain.doFilter} call results in an IOException
 * @throws ServletException if the underlying {@code chain.doFilter} call results in a ServletException
 * @since 1.0
 */
// 當前為SpringShiroFilter攔截器的父類AbstractShiroFilter的executeChain方法
protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain)
        throws IOException, ServletException {
    // 獲取當前請求對應的攔截器鏈
    FilterChain chain = getExecutionChain(request, response, origChain);
    // 執行攔截器鏈
    chain.doFilter(request, response);
}
/**
 * Returns the {@code FilterChain} to execute for the given request.
 * <p/>
 * The {@code origChain} argument is the
 * original {@code FilterChain} supplied by the Servlet Container, but it may be modified to provide
 * more behavior by pre-pending further chains according to the Shiro configuration.
 * <p/>
 * This implementation returns the chain that will actually be executed by acquiring the chain from a
 * {@link #getFilterChainResolver() filterChainResolver}.  The resolver determines exactly which chain to
 * execute, typically based on URL configuration.  If no chain is returned from the resolver call
 * (returns {@code null}), then the {@code origChain} will be returned by default.
 *
 * @param request   the incoming ServletRequest
 * @param response  the outgoing ServletResponse
 * @param origChain the original {@code FilterChain} provided by the Servlet Container
 * @return the {@link FilterChain} to execute for the given request
 * @since 1.0
 */
// 當前為SpringShiroFilter攔截器的父類AbstractShiroFilter的getExecuteChain方法
protected FilterChain getExecutionChain(ServletRequest request, ServletResponse response, FilterChain origChain) {
    FilterChain chain = origChain;
    // 獲取預設的攔截器鏈解析例項:即初始化時設定的PathMatchingFilterChainResolver例項
    FilterChainResolver resolver = getFilterChainResolver();
    if (resolver == null) {
        log.debug("No FilterChainResolver configured.  Returning original FilterChain.");
        return origChain;
    }
    // 通過PathMatchingFilterChainResolver解析當前訪問路徑獲取對應的攔截器鏈
    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;
}
接下來看看Shiro如何解析並獲得指定的攔截器鏈:
// 當前方法為PathMatchingFilterChainResolver的getChain方法
public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) {
    // 獲取攔截器鏈管理器例項:即初始化時設定的DefaultFilterChainManager例項
    FilterChainManager filterChainManager = getFilterChainManager();
    if (!filterChainManager.hasChains()) {
        return null;
    }
    // 獲取當前請求訪問的URI路徑
    String requestURI = getPathWithinApplication(request);
    //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)) {
            if (log.isTraceEnabled()) {
                log.trace("Matched path pattern [" + pathPattern + "] for requestURI [" + requestURI + "].  " +
                        "Utilizing corresponding filter chain...");
            }
            // 如果當前訪問URI路徑與配置的攔截器鏈的攔截路徑匹配則返回代理後的攔截器鏈
            return filterChainManager.proxy(originalChain, pathPattern);
        }
    }
    return null;
}
再來看Shiro如何對攔截器鏈進行代理:
/**
 * @param original 原始攔截器鏈
 * @param chainName 攔截器鏈名稱(實際就是攔截路徑)
 */
// 當前為DefaultFilterChainManager的proxy方法
public FilterChain proxy(FilterChain original, String chainName) {
    // 獲取指定攔截路徑對應的攔截器集合
    NamedFilterList configured = getChain(chainName);
    if (configured == null) {
        String msg = "There is no configured chain under the name/key [" + chainName + "].";
        throw new IllegalArgumentException(msg);
    }
    // 返回代理後的攔截器鏈
    return configured.proxy(original);
}

// DefaultFilterChainManager的getChain方法
public NamedFilterList getChain(String chainName) {
    // filterChains就是一個Map<String, NamedFilterList>屬性
    // NamedFilterList是Shiro存放攔截器鏈的繼承List介面的集合
    return this.filterChains.get(chainName);
}
NamedFilterList介面聲明瞭2個方法:getName()和proxy() 如下:
/**
 * A {@code NamedFilterList} is a {@code List} of {@code Filter} instances that is uniquely identified by a
 * {@link #getName() name}.  It has the ability to generate new {@link FilterChain} instances reflecting this list's
 * filter order via the {@link #proxy proxy} method.
 *
 * @since 1.0
 */
public interface NamedFilterList extends List<Filter> {

    /**
     * Returns the configuration-unique name assigned to this {@code Filter} list.
     *
     * @return the configuration-unique name assigned to this {@code Filter} list.
     */
    String getName();

    /**
     * Returns a new {@code FilterChain} instance that will first execute this list's {@code Filter}s (in list order)
     * and end with the execution of the given {@code filterChain} instance.
     *
     * @param filterChain the {@code FilterChain} instance to execute after this list's {@code Filter}s have executed.
     * @return a new {@code FilterChain} instance that will first execute this list's {@code Filter}s (in list order)
     *         and end with the execution of the given {@code filterChain} instance.
     */
    FilterChain proxy(FilterChain filterChain);
}
Shiro提供了NamedFilterList介面的通用實現SimpleNamedFilterList,所以最後實際呼叫的是該類的proxy()方法:
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.shiro.web.filter.mgt;

import org.apache.shiro.util.StringUtils;
import org.apache.shiro.web.servlet.ProxiedFilterChain;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import java.util.*;

/**
 * Simple {@code NamedFilterList} implementation that is supported by a backing {@link List} instance and a simple
 * {@link #getName() name} property. All {@link List} method implementations are immediately delegated to the
 * wrapped backing list.
 *
 * @since 1.0
 */
public class SimpleNamedFilterList implements NamedFilterList {

    private String name;
    private List<Filter> backingList;

    /**
     * Creates a new {@code SimpleNamedFilterList} instance with the specified {@code name}, defaulting to a new
     * {@link ArrayList ArrayList} instance as the backing list.
     *
     * @param name the name to assign to this instance.
     * @throws IllegalArgumentException if {@code name} is null or empty.
     */
    public SimpleNamedFilterList(String name) {
        this(name, new ArrayList<Filter>());
    }

    /**
     * Creates a new {@code SimpleNamedFilterList} instance with the specified {@code name} and {@code backingList}.
     *
     * @param name        the name to assign to this instance.
     * @param backingList the list instance used to back all of this class's {@link List} method implementations.
     * @throws IllegalArgumentException if {@code name} is null or empty.
     * @throws NullPointerException     if the backing list is {@code null}.
     */
    public SimpleNamedFilterList(String name, List<Filter> backingList) {
        if (backingList == null) {
            throw new NullPointerException("backingList constructor argument cannot be null.");
        }
        this.backingList = backingList;
        setName(name);
    }

    protected void setName(String name) {
        if (!StringUtils.hasText(name)) {
            throw new IllegalArgumentException("Cannot specify a null or empty name.");
        }
        this.name = name;
    }

    public String getName() {
        return name;
    }

    // 具體代理攔截器鏈的方法
    public FilterChain proxy(FilterChain orig) {
        return new ProxiedFilterChain(orig, this);
    }

    public boolean add(Filter filter) {
        return this.backingList.add(filter);
    }

    public void add(int index, Filter filter) {
        this.backingList.add(index, filter);
    }

    public boolean addAll(Collection<? extends Filter> c) {
        return this.backingList.addAll(c);
    }

    public boolean addAll(int index, Collection<? extends Filter> c) {
        return this.backingList.addAll(index, c);
    }

    public void clear() {
        this.backingList.clear();
    }

    public boolean contains(Object o) {
        return this.backingList.contains(o);
    }

    public boolean containsAll(Collection<?> c) {
        return this.backingList.containsAll(c);
    }

    public Filter get(int index) {
        return this.backingList.get(index);
    }

    public int indexOf(Object o) {
        return this.backingList.indexOf(o);
    }

    public boolean isEmpty() {
        return this.backingList.isEmpty();
    }

    public Iterator<Filter> iterator() {
        return this.backingList.iterator();
    }

    public int lastIndexOf(Object o) {
        return this.backingList.lastIndexOf(o);
    }

    public ListIterator<Filter> listIterator() {
        return this.backingList.listIterator();
    }

    public ListIterator<Filter> listIterator(int index) {
        return this.backingList.listIterator(index);
    }

    public Filter remove(int index) {
        return this.backingList.remove(index);
    }

    public boolean remove(Object o) {
        return this.backingList.remove(o);
    }

    public boolean removeAll(Collection<?> c) {
        return this.backingList.removeAll(c);
    }

    public boolean retainAll(Collection<?> c) {
        return this.backingList.retainAll(c);
    }

    public Filter set(int index, Filter filter) {
        return this.backingList.set(index, filter);
    }

    public int size() {
        return this.backingList.size();
    }

    public List<Filter> subList(int fromIndex, int toIndex) {
        return this.backingList.subList(fromIndex, toIndex);
    }

    public Object[] toArray() {
        return this.backingList.toArray();
    }

    public <T> T[] toArray(T[] a) {
        //noinspection SuspiciousToArrayCall
        return this.backingList.toArray(a);
    }
}
可以看到執行proxy()方法後實際返回的是Shiro提供的ProxiedFilterChain例項,該類如下:
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.shiro.web.servlet;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.*;
import java.io.IOException;
import java.util.List;

/**
 * A proxied filter chain is a {@link FilterChain} instance that proxies an original {@link FilterChain} as well
 * as a {@link List List} of other {@link Filter Filter}s that might need to execute prior to the final wrapped
 * original chain.  It allows a list of filters to execute before continuing the original (proxied)
 * {@code FilterChain} instance.
 *
 * @since 0.9
 */
public class ProxiedFilterChain implements FilterChain {

    //TODO - complete JavaDoc

    private static final Logger log = LoggerFactory.getLogger(ProxiedFilterChain.class);

    private FilterChain orig;// 原始攔截器鏈
    private List<Filter> filters;// 原始攔截器鏈對應的攔截器集合
    private int index = 0;// 當前攔截器索引

    // 構造攔截器鏈代理例項
    public ProxiedFilterChain(FilterChain orig, List<Filter> filters) {
        if (orig == null) {
            throw new NullPointerException("original FilterChain cannot be null.");
        }
        this.orig = orig;
        this.filters = filters;
        this.index = 0;
    }

    // 這個方法在攔截器鏈執行過程中被遞迴呼叫
    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);
        }
    }
}
迴歸主線,在過取代理攔截器鏈後,返回executeChain方法,開始執行攔截過濾:chain.doFilter(),這個chain既然是代理攔截器鏈,呼叫的doFilter方法就是上面提到的ProxiedFilterChain的doFilter方法。這裡的filters對應的就是當前攔截器鏈對應的攔截器集合,在DefaultFilterChainManager中的getChain方法中設定,DefaultFilterChainManager的攔截器對映集合在解析配置檔案時進行初始化。由於訪問路徑為"http://127.0.0.1:8080/shiro02/",則匹配的攔截器鏈就是配置的"/**=user",所以當前攔截器鏈的攔截器集合中只有一個攔截器:UserFilter,執行如下程式碼即遍歷攔截器集合遞迴呼叫攔截器的攔截方法:
this.filters.get(this.index++).doFilter(request, response, this);
首先呼叫UserFilter的doFilter()方法,由Shiro攔截器類繼承結構圖可知,這裡呼叫的實際是其父類OncePerRequestFilter的doFilter方法,又好像回到了初次執行SpringShiroFilter的doFilter()方法的時候,具體程式碼參考以上。首先判斷如果沒有執行過並且設定開啟攔截,才執行doFilterInternal()方法,然後標記為執行過,一次請求完成後清除是否已執行標記。不同的是UserFilter呼叫的是其父類AdviceFilter的doFilterInternal()方法。
/**
 * Actually implements the chain execution logic, utilizing
 * {@link #preHandle(javax.servlet.ServletRequest, javax.servlet.ServletResponse) pre},
 * {@link #postHandle(javax.servlet.ServletRequest, javax.servlet.ServletResponse) post}, and
 * {@link #afterCompletion(javax.servlet.ServletRequest, javax.servlet.ServletResponse, Exception) after}
 * advice hooks.
 *
 * @param request  the incoming ServletRequest
 * @param response the outgoing ServletResponse
 * @param chain    the filter chain to execute
 * @throws ServletException if a servlet-related error occurs
 * @throws IOException      if an IO error occurs
 */
// AdviceFilter的doFilterInternal()方法
public void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
        throws ServletException, IOException {

    Exception exception = null;

    try {
        // 前置處理:這裡呼叫的是其父類PathMatchingFilter重寫的perHandle()
        boolean continueChain = preHandle(request, response);
        if (log.isTraceEnabled()) {
            log.trace("Invoked preHandle method.  Continuing chain?: [" + continueChain + "]");
        }
        // 判斷是否執行攔截器鏈的下一個攔截器
        if (continueChain) {
            executeChain(request, response, chain);
        }
        // 後置處理:呼叫的是AdviceFilter的postHandle()(無具體實現)
        postHandle(request, response);
        if (log.isTraceEnabled()) {
            log.trace("Successfully invoked postHandle method");
        }

    } catch (Exception e) {
        exception = e;
    } finally {
        cleanup(request, response, exception);
    }
}
/**
 * Implementation that handles path-matching behavior before a request is evaluated.  If the path matches and
 * the filter
 * {@link #isEnabled(javax.servlet.ServletRequest, javax.servlet.ServletResponse, String, Object) isEnabled} for
 * that path/config, the request will be allowed through via the result from
 * {@link #onPreHandle(javax.servlet.ServletRequest, javax.servlet.ServletResponse, Object) onPreHandle}.  If the
 * path does not match or the filter is not enabled for that path, this filter will allow passthrough immediately
 * to allow the {@code FilterChain} to continue executing.
 * <p/>
 * In order to retain path-matching functionality, subclasses should not override this method if at all
 * possible, and instead override
 * {@link #onPreHandle(javax.servlet.ServletRequest, javax.servlet.ServletResponse, Object) onPreHandle} instead.
 *
 * @param request  the incoming ServletRequest
 * @param response the outgoing ServletResponse
 * @return {@code true} if the filter chain is allowed to continue to execute, {@code false} if a subclass has
 *         handled the request explicitly.
 * @throws Exception if an error occurs
 */
// PathMatchingFilter的perHandle()方法
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
    // 這裡判斷攔截鏈初始化時為當前攔截器(UserFilter)初始化的Map<攔截路徑,配置引數>是否為NULL
    if (this.appliedPaths == null || this.appliedPaths.isEmpty()) {
        if (log.isTraceEnabled()) {
            log.trace("appliedPaths property is null or empty.  This Filter will passthrough immediately.");
        }
        return true;
    }
    // 遍歷當前攔截器(UserFilter)的Map<攔截路徑,配置引數>集合
    for (String path : this.appliedPaths.keySet()) {
        // If the path does match, then pass on to the subclass implementation for specific checks
        //(first match 'wins'):
        // 如果能夠匹配到當前訪問的路徑則進行攔截處理
        if (pathsMatch(path, request)) {
            log.trace("Current requestURI matches pattern '{}'.  Determining filter chain execution...", path);
            // 這裡獲取當前攔截器為該攔截路徑配置的攔截器引數(沒有為null)
            Object config = this.appliedPaths.get(path);
            // 返回給isFilterChainContinued()方法處理
            return isFilterChainContinued(request, response, path, config);
        }
    }
    //no path matched, allow the request to go through: 當前攔截器沒有匹配則放行到下一個攔截器
    return true;
}
/**
 * Simple method to abstract out logic from the preHandle implementation - it was getting a bit unruly.
 *
 * @since 1.2
 */
// PathMatchingFilter的isFilterChainContinued()方法
@SuppressWarnings({"JavaDoc"})
private boolean isFilterChainContinued(ServletRequest request, ServletResponse response,
                                       String path, Object pathConfig) throws Exception {
    // 這裡呼叫的是PathMatchingFilter的isEnabled:判斷當前攔截器是否開啟
    // 最後呼叫的是父類OncePerRequestFilter的isEnabled()方法,判斷屬性enabled==true/false(可配置)
    // 子類可重寫此方法,根據攔截器配置引數決定是否開啟當前攔截器
    if (isEnabled(request, response, path, pathConfig)) { //isEnabled check added in 1.2
        if (log.isTraceEnabled()) {
            log.trace("Filter '{}' is enabled for the current request under path '{}' with config [{}].  " +
                    "Delegating to subclass implementation for 'onPreHandle' check.",
                    new Object[]{getName(), path, pathConfig});
        }
        //The filter is enabled for this specific request, so delegate to subclass implementations
        //so they can decide if the request should continue through the chain or not:
        // 這裡又返回給子類讓其自己去處理,呼叫的是AccessControlFilter的onPreHandle方法
        return onPreHandle(request, response, pathConfig);
    }

    if (log.isTraceEnabled()) {
        log.trace("Filter '{}' is disabled for the current request under path '{}' with config [{}].  " +
                "The next element in the FilterChain will be called immediately.",
                new Object[]{getName(), path, pathConfig});
    }
    //This filter is disabled for this specific request,
    //return 'true' immediately to indicate that the filter will not process the request
    //and let the request/response to continue through the filter chain:
    // 當前攔截器沒有開啟執行攔截器鏈的下一個攔截器
    return true;
}

/**
 * Path-matching version of the parent class's
 * {@link #isEnabled(javax.servlet.ServletRequest, javax.servlet.ServletResponse)} method, but additionally allows
 * for inspection of any path-specific configuration values corresponding to the specified request.  Subclasses
 * may wish to inspect this additional mapped configuration to determine if the filter is enabled or not.
 * <p/>
 * This method's default implementation ignores the {@code path} and {@code mappedValue} arguments and merely
 * returns the value from a call to {@link #isEnabled(javax.servlet.ServletRequest, javax.servlet.ServletResponse)}.
 * It is expected that subclasses override this method if they need to perform enable/disable logic for a specific
 * request based on any path-specific config for the filter instance.
 *
 * @param request     the incoming servlet request
 * @param response    the outbound servlet response
 * @param path        the path matched for the incoming servlet request that has been configured with the given {@code mappedValue}.
 * @param mappedValue the filter-specific config value mapped to this filter in the URL rules mappings for the given {@code path}.
 * @return {@code true} if this filter should filter the specified request, {@code false} if it should let the
 *         request/response pass through immediately to the next element in the {@code FilterChain}.
 * @throws Exception in the case of any error
 * @since 1.2
 */
// PathMatchingFilter的isEnabled()方法
@SuppressWarnings({"UnusedParameters"})
protected boolean isEnabled(ServletRequest request, ServletResponse response, String path, Object mappedValue)
        throws Exception {
    // 這裡呼叫其父類OncePerRequestFilter的isEnabled()方法
    return isEnabled(request, response);
}
/**
 * Returns <code>true</code> if
 * {@link #isAccessAllowed(ServletRequest,ServletResponse,Object) isAccessAllowed(Request,Response,Object)},
 * otherwise returns the result of
 * {@link #onAccessDenied(ServletRequest,ServletResponse,Object) onAccessDenied(Request,Response,Object)}.
 *
 * @return <code>true</code> if
 *         {@link #isAccessAllowed(javax.servlet.ServletRequest, javax.servlet.ServletResponse, Object) isAccessAllowed},
 *         otherwise returns the result of
 *         {@link #onAccessDenied(javax.servlet.ServletRequest, javax.servlet.ServletResponse) onAccessDenied}.
 * @throws Exception if an error occurs.
 */
// AccessControlFilter的onPreHandle方法
public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
    // 方法isAccessAllowed()和onAccessDenied()在AccessControlFilter中均是抽象方法
    // 這裡呼叫的是子類實現的方法isAccessAllowed()和onAccessDenied()
    return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);
}
分析到這,其實最終呼叫了子類(當前為UserFilter)實現的isAccessAllowed() || onAccessDenied() 的結果,UserFilter原始碼:
/**
* Filter that allows access to resources if the accessor is a known user, which is defined as
* having a known principal.  This means that any user who is authenticated or remembered via a
* 'remember me' feature will be allowed access from this filter.
* <p/>
* If the accessor is not a known user, then they will be redirected to the {@link #setLoginUrl(String) loginUrl}</p>
*
* @since 0.9
*/
public class UserFilter extends AccessControlFilter {

    /**
     * Returns <code>true</code> if the request is a
     * {@link #isLoginRequest(javax.servlet.ServletRequest, javax.servlet.ServletResponse) loginRequest} or
     * if the current {@link #getSubject(javax.servlet.ServletRequest, javax.servlet.ServletResponse) subject}
     * is not <code>null</code>, <code>false</code> otherwise.
     *
     * @return <code>true</code> if the request is a
     * {@link #isLoginRequest(javax.servlet.ServletRequest, javax.servlet.ServletResponse) loginRequest} or
     * if the current {@link #getSubject(javax.servlet.ServletRequest, javax.servlet.ServletResponse) subject}
     * is not <code>null</code>, <code>false</code> otherwise.
     */
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        // 如果是登入請求則允許通過:呼叫的是其父類AccessControlFilter的isLoginRequest()方法
        if (isLoginRequest(request, response)) {
            return true;
        } else {
            // 注意:如果session超時,此處獲取subject物件時會丟擲UnknownSessionException
            Subject subject = getSubject(request, response);
            // If principal is not null, then the user is known and should be allowed access.
            // 否則判斷使用者是否已經登入(Rememer me)
            return subject.getPrincipal() != null;
        }
    }

    /**
     * This default implementation simply calls
     * {@link #saveRequestAndRedirectToLogin(javax.servlet.ServletRequest, javax.servlet.ServletResponse) saveRequestAndRedirectToLogin}
     * and then immediately returns <code>false</code>, thereby preventing the chain from continuing so the redirect may
     * execute.
     */
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        // 呼叫父類AccessControlFilter的saveRequestAndRedirectToLogin方法儲存被拒絕的請求並重定向到登入頁,登入成功後再執行
        saveRequestAndRedirectToLogin(request, response);
        return false;
    }
}
顯然isAccessAllowed()方法返回結果為false,然後執行onAccessDenied()方法,這裡呼叫了父類AccessControlFilter的saveRequestAndRedirectToLogin方法,用於儲存被攔截的請求待登入成功後執行。
/**
 * Convenience method for subclasses to use when a login redirect is required.
 * <p/>
 * This implementation simply calls {@link #saveRequest(javax.servlet.ServletRequest) saveRequest(request)}
 * and then {@link #redirectToLogin(javax.servlet.ServletRequest, javax.servlet.ServletResponse) redirectToLogin(request,response)}.
 *
 * @param request  the incoming <code>ServletRequest</code>
 * @param response the outgoing <code>ServletResponse</code>
 * @throws IOException if an error occurs.
 */
// AccessControlFilter類的saveRequestAndRedirectToLogin方法
protected void saveRequestAndRedirectToLogin(ServletRequest request, ServletResponse response) throws IOException {
    saveRequest(request);
    redirectToLogin(request, response);
}

/**
 * Convenience method merely delegates to
 * {@link WebUtils#saveRequest(javax.servlet.ServletRequest) WebUtils.saveRequest(request)} to save the request
 * state for reuse later.  This is mostly used to retain user request state when a redirect is issued to
 * return the user to their originally requested url/resource.
 * <p/>
 * If you need to save and then immediately redirect the user to login, consider using
 * {@link #saveRequestAndRedirectToLogin(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
 * saveRequestAndRedirectToLogin(request,response)} directly.
 *
 * @param request the incoming ServletRequest to save for re-use later (for example, after a redirect).
 */
// AccessControlFilter類的saveRequest方法
protected void saveRequest(ServletRequest request) {
    // 儲存請求
    WebUtils.saveRequest(request);
}

/**
 * Convenience method for subclasses that merely acquires the {@link #getLoginUrl() getLoginUrl} and redirects
 * the request to that url.
 * <p/>
 * <b>N.B.</b>  If you want to issue a redirect with the intention of allowing the user to then return to their
 * originally requested URL, don't use this method directly.  Instead you should call
 * {@link #saveRequestAndRedirectToLogin(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
 * saveRequestAndRedirectToLogin(request,response)}, which will save the current request state so that it can
 * be reconstructed and re-used after a successful login.
 *
 * @param request  the incoming <code>ServletRequest</code>
 * @param response the outgoing <code>ServletResponse</code>
 * @throws IOException if an error occurs.
 */
// AccessControlFilter類的redirectToLogin方法
protected void redirectToLogin(ServletRequest request, ServletResponse response) throws IOException {
    // 配置的重定向的登入地址
    String loginUrl = getLoginUrl();
    // 重定向到loginUrl
    WebUtils.issueRedirect(request, response, loginUrl);
}
請求重定向後,返回結果為return false,最後返回到AdviceFilter的doFilterInternal()方法,告訴攔截器鏈不再繼續執行,本次請求攔截處理結束。具體攔截過程是巢狀呼叫的,即:SpringShiroFilter intercept start ..匹配當前請求路徑的攔截器鏈開始執行..UserFilter intercept start ..if(userFilter.doFilter return true ){ nextFilter intercept start ..
if(nextFilter.doFilter retrun true)...nextFilter intercpt end ..
}else { 終止攔截器執行!!
}UserFilter intercept end .. 
匹配當前請求路徑的攔截器鏈結束執行..SpringShiroFilter intercept end ..由於進行了請求重定向,地址:http://127.0.0.1:8080/shiro02/login.jsp,此時會再次進入shiro攔截器鏈,根據以上配置/login.jsp對應的攔截器鏈為:authc(表單登入攔截器),首先執行FormAuthenticationFilter的doFilter()方法(繼承至父類OncePerRequestFilter的方法),再執行父類AdviceFilter的doFilterInternal方法,與上面提到的UserFilter類似。最終同樣會通過父類AccessControlFilter的onPreHandle()方法類決定是否繼續攔截器鏈的執行:
public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
    return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);
}
只不過isAccessAllowed()與onAccessDenied()方法的具體實現與UserFilter的有所不同,isAccessAllowed()呼叫的是FormAuthenticationFilter其父類AuthenticatingFilter的實現,而onAccessDenied()則是FormAuthenticationFilter自己實現:
/**
 * Determines whether the current subject should be allowed to make the current request.
 * <p/>
 * The default implementation returns <code>true</code> if the user is authenticated.  Will also return
 * <code>true</code> if the {@link #isLoginRequest} returns false and the "permissive" flag is set.
 *
 * @return <code>true</code> if request should be allowed access
 */
// AuthenticatingFilter的isAccessAllowed方法
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
    // 首先呼叫父類isAccessAllowed方法判斷是否已經登入認證
    // 再判斷如果是非登入請求並且攔截器配置為不攔截所有請求
    return super.isAccessAllowed(request, response, mappedValue) ||
            (!isLoginRequest(re