一、环境准备
搭建好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的拦截器基本架构。
(-- 关于各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<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>();
// 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(request, response) && isPermissive(mappedValue)); } /** * Returns <code>true</code> if the mappedValue contains the {@link #PERMISSIVE} qualifier. * * @return <code>true</code> 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 <code>true</code> if the request is an HTTP <code>POST</code>, * <code>false</code> otherwise. Can be overridden by subclasses for custom login submission detection behavior. * * @param request the incoming ServletRequest * @param response the outgoing ServletResponse. * @return <code>true</code> if the request is an HTTP <code>POST</code>, <code>false</code> 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 * <code>{@link org.apache.shiro.web.util.WebUtils WebUtils}.{@link WebUtils#redirectToSavedRequest(javax.servlet.ServletRequest, javax.servlet.ServletResponse, String) redirectToSavedRequest}</code> * 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配置如下:
<!-- 该标签用于简化ParameterizableViewController映射到视图的配置,当响应视图是不需要执行控制器逻辑时使用 path="/hello" 就是你访问的路径,相当于RequestMapping("/hello") view-name="hello"是你所要的视图,如hello.jsp,相当于return "hello" 或 "redirect:hello" --> <mvc:view-controller path="/" view-name="index"/>