Shiro的Filter机制详解---源码分析
Shiro的Filter机制详解
首先从spring-shiro.xml的filter配置说起,先回答两个问题:
1, 为什么相同url规则,后面定义的会覆盖前面定义的(执行的时候只执行最后一个)。
2, 为什么两个url规则都可以匹配同一个url,只执行第一个呢。
下面分别从这两个问题入手,最终阅读源码得到解答。
问题一解答
相同url但定义在不同的行,后面覆盖前面
如
/usr/login.do=test3
/usr/login.do=test1,test2
不会执行test3的filter
要解答第一个问题,需要知道shiro(或者说是spring)是如何扫描这些url规则并保存的。
Web.xml配置shiro以及spring-shiro.xml的核心配置
在web.xml中定义shiroFilter
<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>
在spring-shiro.xml中定义shiroFilter
(要和web.xml中的名称一样,因为spring就是依靠名称来获取这个bean的)
<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内部是怎么将工作委派的,暂时没有分析。
现在关注的是当spring把具体工作委派给ShiroFilterFactoryBean后,该类是怎么工作的。
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
构造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也已经实例化完毕。
构造SpingShiroFilter
在创建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

Shiro的Filter机制详解---源码分析的更多相关文章
- Shiro的Filter机制详解---源码分析(转)
Shiro的Filter机制详解 首先从spring-shiro.xml的filter配置说起,先回答两个问题: 1, 为什么相同url规则,后面定义的会覆盖前面定义的(执行的时候只执行最后一个). ...
- ArrayList详解-源码分析
ArrayList详解-源码分析 1. 概述 在平时的开发中,用到最多的集合应该就是ArrayList了,本篇文章将结合源代码来学习ArrayList. ArrayList是基于数组实现的集合列表 支 ...
- LinkedList详解-源码分析
LinkedList详解-源码分析 LinkedList是List接口的第二个具体的实现类,第一个是ArrayList,前面一篇文章已经总结过了,下面我们来结合源码,学习LinkedList. 基于双 ...
- Java开源生鲜电商平台-盈利模式详解(源码可下载)
Java开源生鲜电商平台-盈利模式详解(源码可下载) 该平台提供一个联合买家与卖家的一个平台.(类似淘宝购物,这里指的是食材的购买.) 平台有以下的盈利模式:(类似的平台有美菜网,食材网等) 1. 订 ...
- quartz集群调度机制调研及源码分析---转载
quartz2.2.1集群调度机制调研及源码分析引言quartz集群架构调度器实例化调度过程触发器的获取触发trigger:Job执行过程:总结:附: 引言 quratz是目前最为成熟,使用最广泛的j ...
- 定时组件quartz系列<三>quartz调度机制调研及源码分析
quartz2.2.1集群调度机制调研及源码分析引言quartz集群架构调度器实例化调度过程触发器的获取触发trigger:Job执行过程:总结:附: 引言 quratz是目前最为成熟,使用最广泛的j ...
- (1)quartz集群调度机制调研及源码分析---转载
quartz2.2.1集群调度机制调研及源码分析 原文地址:http://demo.netfoucs.com/gklifg/article/details/27090179 引言quartz集群架构调 ...
- 微服务生态组件之Spring Cloud LoadBalancer详解和源码分析
Spring Cloud LoadBalancer 概述 Spring Cloud LoadBalancer目前Spring官方是放在spring-cloud-commons里,Spring Clou ...
- Go Revel - Filter(过滤器)源码分析
在 Go Revel - server.go 源码分析 http://www.cnblogs.com/hangxin1940/p/3265538.html 说到revel框架很多重要的东西都Filte ...
随机推荐
- [Java]ArrayList集合的contains方法
用到集合ArrayList时经常会用到里面自带的方法boolean contains(Object o);此方法用于判断集合里面是否包含元素o,现在讨论下在Object类型为类类型的时候的情况: cl ...
- Hibernate中OpenSessionInViewFilter(通常配置在web.xml文件中)的作用
Spring为我们解决Hibernate的Session的关闭与开启问题. Hibernate 允许对关联对象.属性进行延迟加载,但是必须保证延迟加载的操作限于同一个 Hibernate Sessio ...
- Java中取两位小数
请参考下面函数: private String getFormated(String s){ float f=Float.parseFloat(s); java.text. ...
- 【算法33】LRU算法
题目来源 LeetCode: https://leetcode.com/problems/lru-cache/ LRU简介 LRU (Least Recently Used,最近最少使用)算法是操作系 ...
- JS和C#访问遇到QueryInterface调用出错
在原来的WinForm里,我们只要在窗体类的头部添加属性[System.Runtime.InteropServices.ComVisibleAttribute(true)],然后 webBrowser ...
- Win(Phone)10开发第(5)弹,本地媒体服务器的一些注意事项
首先有个wp上的http服务器 http://wphttpserver.codeplex.com/ 使用方式: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ...
- C#中泛型类,泛型方法,泛型约束实际应用
前言 所谓泛型,即通过参数化类型来实现在同一份代码上操作多种数据类型. 泛型编程是一种编程范式,它利用“参数化类型”将类型抽象化,从而实现更为灵活的复用.在定义泛型类时,在对客户端代码能够在实例化类时 ...
- 【cocos2d-x 手游研发----研发思路及感想】
我半年前进入了目前的这家做教育行业的公司(在此之前一直从事原生态开发手游的迷茫之路),学习是一件很快乐的事情,来到这家公司我有了很多时间去学习,不管是公司业务,还是其他技术相关的.于是开始 ...
- JVM内存回收区域+对象存活的判断+引用类型+垃圾回收线程
此文已由作者赵计刚薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 注意:本文主要参考自<深入理解Java虚拟机(第二版)> 说明:查看本文之前,推荐先知道JVM ...
- 如何使用socket进行java网络编程(二)
通过在如何使用socket进行java网络编程(一)中程序的编写,可以总结出一些常用的java socket编程的范例来. ServerSocket server = new ServerSocket ...