shiro学习笔记:整合spring之拦截器链执行流程

一、环境准备

搭建好spring + shiro整合环境(本文环境Spring 4.3.10.RELEASE + Shiro 1.4.0)后,编写登录页面如下:


登录页


${shiroLoginFailure}
用户名:
密码:
shiro拦截器部分配置:
    
    
        
        
        
    
    
    
    
    
        
        
        
        
            
                
            
        
        
            
                /index.jsp = anon
                /unauthorized.jsp = anon
                /error.jsp = anon
                /login.jsp = authc
                /logout = logout
                /** = user
            
        
    
shiro拦截器树状结构如下图:

shiro学习笔记:整合spring之拦截器链执行流程_第1张图片

其中几个主要的拦截:OnceperRequestFilter、AbstractShiroFilter、AdviceFilter、PathMatchingFilter、AccessControlFilter、AuthenticationFilter、AuthorizationFilter等构成shiro的拦截器基本架构。

(-- 关于各shiro各个拦截器的简单介绍可参考博文:Apache Shiro学习笔记(六)Shiro Filter介绍)

二、初始化过程

启动项目时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() {
					@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 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 filters = getFilters();
    // 处理自定义的拦截器
    if (!CollectionUtils.isEmpty(filters)) {
        for (Map.Entry 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 chains = getFilterChainDefinitionMap();
    if (!CollectionUtils.isEmpty(chains)) {
        for (Map.Entry entry : chains.entrySet()) {
            String url = entry.getKey();// 配置的URL
            String chainDefinition = entry.getValue();// 该URL对应的拦截器实例
            // 解析filterChainDefinitions配置:每一个配置的URL对应一个shiro代理拦截器链
            // 并将解析的每一个拦截器链交由DefaultFilterChainManager的Map管理
            manager.createChain(url, chainDefinition);
        }
    }
    return manager;
}
shiro默认拦截器链管理器DefaultFilterChainManager的初始化构造方法:
// DefaultFilterChainManager在初始化时添加默认拦截器
public DefaultFilterChainManager() {
    // 此处初始化拦截器Map<拦截器名称,拦截器实例>:这里保存所有的默认以及自定义的拦截器
    this.filters = new LinkedHashMap();
    // 此处初始化拦截器链Map<拦截器链名称(拦截路径),拦截器集合>:这里保存所有自定义的拦截器链所对应的拦截器集合
    this.filterChains = new LinkedHashMap();
    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 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 filterClass;

    private DefaultFilter(Class filterClass) {
        this.filterClass = filterClass;
    }

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

    public Class getFilterClass() {
        return this.filterClass;
    }

    public static Map createInstanceMap(FilterConfig config) {
        Map filters = new LinkedHashMap(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 config argument and sets the resulting
 * String[] array on the appliedPaths internal Map.
 * 

* That is: *


 * String[] values = null;
 * if (config != null) {
 *     values = split(config);
 * }
 * 

* this.{@link #appliedPaths appliedPaths}.put(path, values); *

* * @param path the application context path to match for executing this filter. * @param config the specified for this particular filter only for the given path * @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. *

*

To put it another way, the keys are the paths (urls) that this Filter will process. *

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 appliedPaths = new LinkedHashMap();

保险方法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:
 * 
    *
  1. {@link #prepareServletRequest(ServletRequest, ServletResponse, FilterChain) Prepares} * the incoming {@code ServletRequest} for use during Shiro's processing
  2. *
  3. {@link #prepareServletResponse(ServletRequest, ServletResponse, FilterChain) Prepares} * the outgoing {@code ServletResponse} for use during Shiro's processing
  4. *
  5. {@link #createSubject(javax.servlet.ServletRequest, javax.servlet.ServletResponse) Creates} a * {@link Subject} instance based on the specified request/response pair.
  6. *
  7. 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
  8. *
*

* 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.
 * 

* This implementation first delegates to * {@link #getExecutionChain(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) getExecutionChain} * 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: *

 * FilterChain chain = {@link #getExecutionChain}(request, response, origChain);
 * chain.{@link FilterChain#doFilter doFilter}(request,response);
* * @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.
 * 

* 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. *

* 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属性
    // 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 {

    /**
     * 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 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());
    }

    /**
     * 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 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 c) {
        return this.backingList.addAll(c);
    }

    public boolean addAll(int index, Collection 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 iterator() {
        return this.backingList.iterator();
    }

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

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

    public ListIterator 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 subList(int fromIndex, int toIndex) {
        return this.backingList.subList(fromIndex, toIndex);
    }

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

    public  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 filters;// 原始拦截器链对应的拦截器集合
    private int index = 0;// 当前拦截器索引

    // 构造拦截器链代理实例
    public ProxiedFilterChain(FilterChain orig, List 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.
 * 

* 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.
 * 

* 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 true 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 true 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.
* 

* If the accessor is not a known user, then they will be redirected to the {@link #setLoginUrl(String) loginUrl}

* * @since 0.9 */ public class UserFilter extends AccessControlFilter { /** * Returns true 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 null, false otherwise. * * @return true 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 null, false 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 false, 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.
 * 

* 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 ServletRequest * @param response the outgoing ServletResponse * @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. *

* 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. *

* N.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 ServletRequest * @param response the outgoing ServletResponse * @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.
 * 

* The default implementation returns true if the user is authenticated. Will also return * true if the {@link #isLoginRequest} returns false and the "permissive" flag is set. * * @return true 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(request, response) && isPermissive(mappedValue)); } /** * Returns true if the mappedValue contains the {@link #PERMISSIVE} qualifier. * * @return true if this filter should be permissive */ // AuthenticatingFilter的isPermissive方法 protected boolean isPermissive(Object mappedValue) { if(mappedValue != null) { String[] values = (String[]) mappedValue; return Arrays.binarySearch(values, PERMISSIVE) >= 0; } return false; }

// FormAuthenticationFilter的onAccessDenied()方法
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
    // 判断如果是登录请求
    if (isLoginRequest(request, response)) {
        // 判断如果是POST请求则执行表单登录操作
        if (isLoginSubmission(request, response)) {
            if (log.isTraceEnabled()) {
                log.trace("Login submission detected.  Attempting to execute login.");
            }
            // 调用父类AuthenticatingFilter的executeLogin
            return executeLogin(request, response);
        // 如果是GET请求则不进行拦截即显示登录页
        } else {
            if (log.isTraceEnabled()) {
                log.trace("Login page view.");
            }
            //allow them to see the login page ;)
            return true;
        }
    } else {
        if (log.isTraceEnabled()) {
            log.trace("Attempting to access a path which requires authentication.  Forwarding to the " +
                    "Authentication url [" + getLoginUrl() + "]");
        }
        // 调用父类AccessControlFilter的saveRequestAndRedirectToLogin方法保存请求
        saveRequestAndRedirectToLogin(request, response);
        return false;
    }
}

/**
 * This default implementation merely returns true if the request is an HTTP POST,
 * false otherwise. Can be overridden by subclasses for custom login submission detection behavior.
 *
 * @param request  the incoming ServletRequest
 * @param response the outgoing ServletResponse.
 * @return true if the request is an HTTP POST, false otherwise.
 */
// FormAuthenticationFilter的isLoginSubmission方法
@SuppressWarnings({"UnusedDeclaration"})
protected boolean isLoginSubmission(ServletRequest request, ServletResponse response) {
    return (request instanceof HttpServletRequest) && WebUtils.toHttp(request).getMethod().equalsIgnoreCase(POST_METHOD);
}
由此可知,isAccessAllowed返回false,onAccessDenied返回true,最终拦截器链返回结果为true,即浏览器显示登录页login.jsp,后续执行流程与上面UserFilter一致。

当在登录页提交登录表单时,实际上是POST请求的"http://127.0.0.1:8080/shiro02/login.jsp",并且请求体内携带登录参数。
因为表单action是空串,所以默认请求的是当前页面地址,如果表单action不为空串,则登录请求将不会被shiro拦截器处理。

由于是POST请求访问的loginUrl,则在表单拦截器的onAccessDenied()方法中会执行父类的executeLogin()进行登录:

// AuthenticatingFilter的executeLogin方法
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
    // 调用FormAuthenticationFilter的创建登录请求token方法
    AuthenticationToken token = createToken(request, response);
    if (token == null) {
        String msg = "createToken method implementation returned null. A valid non-null AuthenticationToken " +
                "must be created in order to execute a login attempt.";
        throw new IllegalStateException(msg);
    }
    try {
        Subject subject = getSubject(request, response);
        // 交给securityManager进行登录处理
        subject.login(token);
        // FormAuthenticationFilter重写的登录成功回调方法
        return onLoginSuccess(token, subject, request, response);
    } catch (AuthenticationException e) {
        // FormAuthenticationFilter重写的登录失败回调方法
        return onLoginFailure(token, e, request, response);
    }
}

// FormAuthenticationFilter重写的onLoginSuccess方法
protected boolean onLoginSuccess(AuthenticationToken token, Subject subject,
                                 ServletRequest request, ServletResponse response) throws Exception {
    // 登录成功重定向(调用父类AuthenticationFilter的issueSuccessRedirect方法)
    issueSuccessRedirect(request, response);
    //we handled the success redirect directly, prevent the chain from continuing:
    return false;
}

// FormAuthenticationFilter重写的onLoginFailure方法
protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e,
                                 ServletRequest request, ServletResponse response) {
    if (log.isDebugEnabled()) {
        log.debug( "Authentication exception", e );
    }
    // 保存登录失败信息,可在jsp使用EL表达式获取
    setFailureAttribute(request, e);
    //login failed, let request continue back to the login page:
    return true;
}

protected void setFailureAttribute(ServletRequest request, AuthenticationException ae) {
    String className = ae.getClass().getName();
    // 保存键值对(键默认为"shiroLoginFailure")
    request.setAttribute(getFailureKeyAttribute(), className);
}
/**
 * Redirects to user to the previously attempted URL after a successful login.  This implementation simply calls
 * {@link org.apache.shiro.web.util.WebUtils WebUtils}.{@link WebUtils#redirectToSavedRequest(javax.servlet.ServletRequest, javax.servlet.ServletResponse, String) redirectToSavedRequest}
 * using the {@link #getSuccessUrl() successUrl} as the {@code fallbackUrl} argument to that call.
 *
 * @param request  the incoming request
 * @param response the outgoing response
 * @throws Exception if there is a problem redirecting.
 */
// AuthenticationFilter的issueSuccessRedirect方法
protected void issueSuccessRedirect(ServletRequest request, ServletResponse response) throws Exception {
    // 重定向到之前保存的请求地址如果没有则重定向到配置的successUrl(默认为"/", 可配置)
    WebUtils.redirectToSavedRequest(request, response, getSuccessUrl());
}
这样,登录成功就会终止拦截器链的执行重定向到successUrl,登录失败就继续拦截器链的执行返回到登录页。本文测试工程没有配置successUrl,所以登录成功后会重定向到"/",此时再次进入shiro拦截器链"/**=user",即只会执行UserFilter拦截器,在UserFilter的isAccessAllowed中会判断是否已登录认证,很明显结果为true(刚刚认证成功),继续执行shiro代理拦截器链(如果还有shiro拦截器)以及其他拦截器链(spring、tomcat等),这里将执行springMVC的拦截器,测试工程在springMVC配置文件配置了访问路径"/"跳转到"index.jsp",故最终跳转到index.jsp完成登录请求。springMVC配置如下:


你可能感兴趣的:(Shiro)