首先从spring-shiro.xml的filter配置提及,先回答两个问题:html
1, 为何相同url规则,后面定义的会覆盖前面定义的(执行的时候只执行最后一个)。web
2, 为何两个url规则均可以匹配同一个url,只执行第一个呢。spring
下面分别从这两个问题入手,最终阅读源码获得解答。apache
相同url但定义在不一样的行,后面覆盖前面session
如app
/usr/login.do=test3 /usr/login.do=test1,test2 不会执行test3的filter
要解答第一个问题,须要知道shiro(或者说是spring)是如何扫描这些url规则并保存的。框架
<filter> <filter-name>shiroFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <init-param> <!-- 该值缺省为false,表示生命周期由SpringApplicationContext管理,设置为true则表示由ServletContainer管理 --> <param-name>targetFilterLifecycle</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>shiroFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
(要和web.xml中的名称同样,由于spring就是依靠名称来获取这个bean的) jsp
<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="/WEB-INF/405.html" /> <property name="filters"> <map> <entry key="kickout" value-ref="kickoutSessionControlFilter" /> </map> </property> <property name="filterChainDefinitions"> <value> /**=kickout /usr/login.do=anon /security/*=anon /usr/login.do=authc /usr/test/*=authc </value> </property> </bean>
都定义好以后,分析org.springframework.web.filter.DelegatingFilterProxy发现该filter类的任务是:将具体工做分派给org.apache.shiro.spring.web.ShiroFilterFactoryBean这个类中的静态内部类SpringShiroFilter作。函数
具体spring内部是怎么将工做委派的,暂时没有分析。post
如今关注的是当spring把具体工做委派给ShiroFilterFactoryBean后,该类是怎么工做的。
在这以前,spring经过bean注入,将ShiroFilterFactoryBean的相关成员经过set方法注入进去。
前面已经配置了filters和filterChainDefinitions,再次贴出以下所示:
<property name="filters"> <map> <entry key="kickout" value-ref="kickoutSessionControlFilter" /> </map> </property> <property name="filterChainDefinitions"> <value> /**=kickout /usr/login.do=anon /security/*=anon /usr/login.do=authc /usr/test/*=authc </value> </property>
看一下ShiroFilterFactoryBean是怎么接收他们的。
Filters很简单,只须要map接收就自动完成了。
public void setFilters(Map<String, Filter> filters) { this.filters = filters; }
可是filterChainDefinitions是String类型的,须要转换(使用了ini转换方法,内部使用LinkedHashMap保存url和filter的映射关系,保证了顺序)
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); } setFilterChainDefinitionMap(section); }
这两步完成后,filters被注入
filterChianDefinitions也被注入,可是注入方法经过shiro自定义了ini方式,
该方式经过LinkedHashMap保存url规则和对应的权限(键值对),因此当写了相同的url规则,后者会覆盖前者(------如今对HashMap的存储规则遗忘了,须要再看一下)
问题一解答完成
同一个url能够匹配不一样的规则,但只执行首行 /usr/* =test1,test2 /usr/login.do=test3 url = /usr/login.do请求来了,不会执行test3,由于已经匹配了/usr/* =test1,test2 要解答该问题,须要知道每一个url的FilterChain是如何获取的
接上分析:
有了filter和filterChainDefinitionMap的数据后,下面的工做是构造FilterChainManager
为何到这一步呢?
查看spring委托机制,最终找到ShiroFilterFactoryBean的createInstance()方法(这个方法是shiro的filter构造机制的主线),因为ShiroFilterFactoryBean 实现了FactoryBean,spring就是经过这个接口的createInstance方法获取到filter实例的,下面是该方法在ShiroFilterFactoryBean中的实现:
protected AbstractShiroFilter createInstance() throws Exception { log.debug("Creating Shiro Filter instance."); SecurityManager securityManager = getSecurityManager(); if (securityManager == null) { String msg = "SecurityManager property must be set."; throw new BeanInitializationException(msg); } if (!(securityManager instanceof WebSecurityManager)) { String msg = "The security manager does not implement the WebSecurityManager interface."; throw new BeanInitializationException(msg); } FilterChainManager manager = createFilterChainManager(); PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver(); chainResolver.setFilterChainManager(manager); return new SpringShiroFilter((WebSecurityManager) securityManager, chainResolver); }
从这里能够知道,首先获取filterChainManager,具体方法以下
protected FilterChainManager createFilterChainManager() { DefaultFilterChainManager manager = new DefaultFilterChainManager(); Map<String, Filter> defaultFilters = manager.getFilters(); //apply global settings if necessary: for (Filter filter : defaultFilters.values()) { applyGlobalPropertiesIfNecessary(filter); } //Apply the acquired and/or configured filters: Map<String, Filter> filters = getFilters(); if (!CollectionUtils.isEmpty(filters)) { for (Map.Entry<String, Filter> entry : filters.entrySet()) { String name = entry.getKey(); Filter filter = entry.getValue(); applyGlobalPropertiesIfNecessary(filter); if (filter instanceof Nameable) { ((Nameable) filter).setName(name); } //'init' argument is false, since Spring-configured filters should be initialized //in Spring (i.e. 'init-method=blah') or implement InitializingBean: manager.addFilter(name, filter, false); } } //build up the chains: Map<String, String> chains = getFilterChainDefinitionMap(); if (!CollectionUtils.isEmpty(chains)) { for (Map.Entry<String, String> entry : chains.entrySet()) { String url = entry.getKey(); String chainDefinition = entry.getValue(); manager.createChain(url, chainDefinition); } } return manager; }
分析后得知,首先在createFilterChainManager()方法中,建立一个DefaultFilterChainManager对象,而这个对象的构造函数在最后会将DefaultFilter中定义的shiro默认的filter映射加入到该对象中。以下代码就是DefaultFilter的定义。
在DefaultFilterChainManager中还作了一件事就是url-filter的映射变成filterChain,这句代码就是执行这个任务(将咱们在xml文件中定义的filterChainDefinitions变成filterChain)。
manager.createChain(url, chainDefinition);
做用是将权限分割:如
"authc, roles[admin,user], perms[file:edit]"
将会被分割为
{ "authc", "roles[admin,user]", "perms[file:edit]" }
具体的源代码以下:
public void createChain(String chainName, String 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); for (String token : filterTokens) { String[] nameConfigPair = toNameConfigPair(token); addToChain(chainName, nameConfigPair[0], nameConfigPair[1]); } }
而且经过toNameConfigPair(token)将如:roles[admin,user]形式的变成roles,admin,user形式的分割
而后根据url规则 映射 权限和角色
能够发现,每次分割一个token,都会经过addToChain方法接受
分析public void addToChain(String chainName, String filterName, String chainSpecificFilterConfig)方法
public void addToChain(String chainName, String filterName, String chainSpecificFilterConfig) { if (!StringUtils.hasText(chainName)) { throw new IllegalArgumentException("chainName cannot be null or empty."); } Filter filter = getFilter(filterName); 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); NamedFilterList chain = ensureChain(chainName); chain.add(filter); }
分析applyChainConfig(chainName, filter, chainSpecificFilterConfig);
protected void applyChainConfig(String chainName, Filter filter, String chainSpecificFilterConfig) { //…………………………. if (filter instanceof PathConfigProcessor) { ((PathConfigProcessor) filter).processPathConfig(chainName, chainSpecificFilterConfig); } else { if (StringUtils.hasText(chainSpecificFilterConfig)) { //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); } } }
因为咱们自定义的filter都是PathMatchingFilter的子类,因此在applyChainConfig方法中完成的就是将url添加到filter的url表中。
在PathMatchingFilter中能够发现
protected Map<String, Object> appliedPaths = new LinkedHashMap<String, Object>();
processPathConfig 方法的实现以下
public Filter processPathConfig(String path, String config) { String[] values = null; if (config != null) { values = split(config); } this.appliedPaths.put(path, values); return this; }
基本上在spring-shiro.xml中定义filter的载入过程已经阅读完成,
1, 定义一个DefaultFilterChainManager对象
2, 首先加载默认的filter
3, 加载xml文件中定义的filter
4, 加载xml文件定义的url和filter映射关系
5, 将映射关系解析为以url为键,NamedFilterList为值的键值对。
6, 在解析的过程当中,对每一个url和对应的过滤条件,都会放到对应filter的appliedPaths中(在PathMatchingFilter中定义)。
如今FilterChainManager的对象已经建立完毕,而且每一个filter也已经实例化完毕。
在建立SpringShiroFilter以前还要将刚才建立的FilterChainManager对象包装成一个PathMatchingFilterChainResolver对象(注释的意思是:不直接将FilterChainManager对象暴露给AbstractShiroFilter的实现者,在这里就是SpringShiroFilter。)
PathMatchingFilterChainResolver最重要的做用是:当请求url来的时候,他担任匹配工做(调用该类的getChain方法作匹配,暂时先不分析该方法,等知道在哪里调用该方法时候再分析。其实问题二此时已经能够解答,经过该方法就能够知道,某个url匹配到过滤链的第一个规则时就return了。)
上图最后一句话执行完成后,一个SpringShiroFilter建立完毕。
下面分析当url请求到来的时候,shiro是如何完成过滤的。首先经过图片大体的了解一下。
如今分析AbstractShiroFilter的doFilterInternal()方法
protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain) throws ServletException, IOException { Throwable t = null; try { final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain); final ServletResponse response = prepareServletResponse(request, servletResponse, chain); final Subject subject = createSubject(request, response); subject.execute(new Callable() { public Object call() throws Exception { updateSessionLastAccessTime(request, response); executeChain(request, response, chain); return null; } }); } catch (ExecutionException ex) { t = ex.getCause(); } catch (Throwable throwable) { t = throwable; } //………… }
暂时不关心subject相关的建立等过程,只关心这行代码
executeChain(request, response, chain);
具体实现以下
protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain) throws IOException, ServletException { FilterChain chain = getExecutionChain(request, response, origChain); chain.doFilter(request, response); }
再看getExecutionChain(request, response, origChain);具体实现以下:
protected FilterChain getExecutionChain(ServletRequest request, ServletResponse response, FilterChain origChain) { FilterChain chain = origChain; FilterChainResolver resolver = getFilterChainResolver(); if (resolver == null) { log.debug("No FilterChainResolver configured. Returning original FilterChain."); return origChain; } FilterChain resolved = resolver.getChain(request, response, origChain); if (resolved != null) { log.trace("Resolved a configured FilterChain for the current request."); chain = resolved; } else { log.trace("No FilterChain configured for the current request. Using the default."); } return chain; }
能够发现,这里用到了咱们在建立SpringShiroFilter时传递的FilterChainResolver,至此,咱们终于找到了getChain()方法在这里被调用了。其源码实现以下
public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) { FilterChainManager filterChainManager = getFilterChainManager(); if (!filterChainManager.hasChains()) { return null; } 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..."); } return filterChainManager.proxy(originalChain, pathPattern); } } return null; }
从for循环能够看出,当匹配到第一个url规则,则return一个表明这个url规则的FilterChain给web容器执行。
问题二解答:每一个url在匹配他的FilterChain时,当匹配到第一个URL规则时,就返回。
FilterChain的实现类为org.apache.shiro.web.servlet.ProxiedFilterChain
从该类的doFilter方法能够知道,它会将Filter链的Filter的doFilter方法顺序执行一遍。下图展现了这一过程
如今只须要分析每一个Filter的doFilter方法就好了。
先看一下shiro整个filter框架继承关系(图片来自第八章 拦截器机制——《跟我学Shiro》)
上面是它的继承关系:最终的doFilter方法在OncePerRequestFilter中实现,具体代码以下:
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); } 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 { 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(request, response, filterChain);来完成具体的过滤操做,doFilterInternal方法在 SpringShiroFilter的直接父类AbstractShiroFilter的具体实现过程已经在上面分析过了:具体的就是shiro真正验证受权前的subject,session等初始化的工做,使得后面的过滤以及验证受权工做能够获得subject等而后正常工做。完成后调用其余shiro filter进行继续过滤
而除了shiroFilter以外,其他的filter都是AdviceFilter分支的子类。刚才看了AbstractShiroFilter的doFilterInternal方法,如今看一下AdviceFilter对该方法的实现:
public void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException { Exception exception = null; try { boolean continueChain = preHandle(request, response); if (log.isTraceEnabled()) { log.trace("Invoked preHandle method. Continuing chain?: [" + continueChain + "]"); } if (continueChain) { executeChain(request, response, chain); } postHandle(request, response); if (log.isTraceEnabled()) { log.trace("Successfully invoked postHandle method"); } } catch (Exception e) { exception = e; } finally { cleanup(request, response, exception); } }
与AbstractShiroFilter的doFilterInternal方法不一样的是,这里经过continueChain变量来判断到底后续的filter会不会被继续执行。而该变量的值由preHandle()函数决定。
基本上全部在系统中用到的filter都是继承PathMatchingFilter类的。看一下该类的preHandle()函数实现,能够发现,咱们在xml文件中定义的url匹配,在这里面能够看到匹配原则了:
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception { 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; } 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); Object config = this.appliedPaths.get(path); return isFilterChainContinued(request, response, path, config); } } //no path matched, allow the request to go through: return true; }
继续调用isFilterChainContinued(request, response, path, config)--> onPreHandle(request, response, pathConfig);
分析onPreHandle(),PathMatchingFilter本身并无实现,只是简单的返回true。因此当咱们自定义filter的时候,要将具体的逻辑实如今该方法中,或者实现该类的子类AccessControlFilter(该类对onPreHandle()方法进行了更细致的划分,大部分通常会继承该类)
有兴趣的能够分析一下shiro自带的这些filter