目录
概述
OncePerRequestFilter
我们知道filter都是通过doFilter方法处理相关逻辑。通过源码可以发现,shiro所有的filter的doFilter方法只有OncePerRequestFilter中有。OncePerRequestFilter也是我们平时所用到的shiro的filter的抽象父类。且该方法是final的。
public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
if ( request.getAttribute(alreadyFilteredAttributeName) != null ) {
filterChain.doFilter(request, response);
} else if (!isEnabled(request, response) || shouldNotFilter(request) ) {
filterChain.doFilter(request, response);
} else {
request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
try {
doFilterInternal(request, response, filterChain);
} finally {
request.removeAttribute(alreadyFilteredAttributeName);
}
}
}
- alreadyFilteredAttributeName :标记该filter是否处理过请求。处理过则继续执行过滤器链上的其它filter。
- isEnabled():如果此过滤器应该过滤指定的请求,则返回true ,如果它应该将request/response立即传递到FilterChain的下一个元素,则返回false 。默认情况下为true (以确保过滤器始终默认执行),但如有必要,它可以被子类覆盖以实现特定于请求的行为。 例如,可以根据访问的请求路径启用或禁用过滤器。
- shouldNotFilter:已废弃。用于子类覆盖。请使用isEnabled。
- doFilterInternal:真正的处理过滤器逻辑方法
shiro中有两个子过滤器实现了doFilterInternal方法。AdviceFilter和AbstractShiroFilter。
AdviceFilter
一个 Servlet 过滤器,它通过preHandle 、 postHandle和afterCompletion钩子为 ServletRequest 启用 AOP 风格的“环绕”建议。
public void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
throws ServletException, IOException {
Exception exception = null;
try {
boolean continueChain = preHandle(request, response);
if (continueChain) {
executeChain(request, response, chain);
}
postHandle(request, response);
} catch (Exception e) {
exception = e;
} finally {
cleanup(request, response, exception);
}
}
- preHandle:如果应该允许过滤器链继续,则返回true ,否则返回false 。 在过滤器执行链执行下一个filter之前调用它。默认返回true。(返回false,则过滤器不往下执行了,则请求的接口也就触发不了(后边详细说明),shiro的认证就在此阶段完成)
- executeChain:执行过滤器链。shiro通过调用自己的FilterChain(里边包装了原始的FilterChain)的chain.doFilter(request, response)方法,将我们通过spring配置给shiro的所有filter执行完成后,再调用原始的FilterChain的chain.doFilter(request, response)方法。后面会详细说明
- postHandle:在目标方法(请求的接口)正常(未抛出异常)执行后完成一些操作,默认不做任何操作。
- cleanup:在doFilterInternal实现的finally代码块中执行清理逻辑。此实现专门调用afterCompletion并正确处理任何异常。
- afterCompletion:大多数资源“清理”行为通常在此实现中完成,即使链处理抛出异常,也保证为每个请求调用它。
这里我们重点看下子类PathMatchingFilter对于preHandle的实现
PathMatchingFilter
处理指定路径并允许所有其他路径通过。主要是对preHandle做进一步细化控制。如果是需要处理的路径,则调用isFilterChainContinued做进一步的控制。该方法中会调用onPreHandle方法。默认返回true,子类可以覆盖。
子类:
- AnonymousFilter:onPreHandle直接返回true。这个filter就是我们配置anon不需要认证时的filter。因为直接返回true,所以不需要认证。
- AccessControlFilter:重点说明
AccessControlFilter
控制对资源的访问的任何过滤器的超类,如果用户未经身份验证,可能会将用户重定向到登录页面。
public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);
}
AccessControlFilter中对onPreHandle方法做了进一步细化。通过isAccessAllowed和 onAccessDenied方法进一步细化控制。
- isAccessAllowed:如果允许请求正常通过过滤器,则返回true ,返回false如果请求应由onAccessDenied处理。抽象方法,需要子类实现。
-
onAccessDenied:处理由isAccessAllowed方法拒绝访问的请求。此方法立即委托给onAccessDenied(ServletRequest, ServletResponse)抽象方法,需要子类实现。
AccessControlFilter后续的子类主要就是针对上述抽象方法的实现达到控制效果。
基于Spring的Web应用
ShiroFilterFactoryBean
FactoryBean在基于 Spring 的 Web 应用程序中用于定义主 Shiro 过滤器。可以添加自定义的过滤器。
getObject():通过createInstance方法懒惰地创建并返回一个AbstractShiroFilter具体实例SpringShiroFilter。
SpringShiroFilter的反向构造过程(...表示前者中持有后者)。可以看出该filter中包含了shiro添加的filter。通过源码不难发现,只有添加到filterChainDefinitionMap的filter的filter定义才会被真正的执行(添加到NamedFilterList中)。所以完全可以自定义filter而不触发shiro自带的filter(shiro的DefaultFilter枚举可以找到)
SpringShiroFilter...PathMatchingFilterChainResolver...FilterChainManager
...NamedFilterList...filters
SpringShiroFilter执行流程
SpringShiroFilter的doFilter由servlet容器触发。文章一开头说了shiro的doFilter只有OncePerRequestFilter中有。所以看doFilterInternal真正的处理过滤器逻辑。该方法在父类AbstractShiroFilter中。
protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain)
throws ServletException, IOException {
try {
final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);
final ServletResponse response = prepareServletResponse(request, servletResponse, chain);
final Subject subject = createSubject(request, response);
//noinspection unchecked
subject.execute(new Callable() {
public Object call() throws Exception {
updateSessionLastAccessTime(request, response);
executeChain(request, response, chain);
return null;
}
});
}
}
创建了Subject,调用executeChain执行链。(重点)
protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain)
throws IOException, ServletException {
FilterChain chain = getExecutionChain(request, response, origChain);
chain.doFilter(request, response);
}
从方法的参数命名中就可以看出,该方法的参数FilterChain命名为origChain原始的filterChain,也就是servlet提供的。而方法内部则通过原始的origChain得到了一个新的chain(文章上边所说的shiro自己的filterChain)。然后调用新的chain的doFilter方法。(当然也有可能返回的还是原始的filterChain。getExecutionChain方法一开始有个判断,自行查看,这里不考虑,因为通过SpringShiroFilter的创建过程会发现那个判断不成立)。getExecutionChain方法后续的逻辑其实就是上边SpringShiroFilter的反向构造过程的正向获取过程。最后调用的就是NamedFilterList的实现类SimpleNamedFilterList的proxy方法。NamedFilterList中有需要执行的所有filter。
public FilterChain proxy(FilterChain orig) {
return new ProxiedFilterChain(orig, this);
}
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;
}
可以看出 ProxiedFilterChain中持有原始的filterChain.且持有所有需要执行的filter。
紧接上面的chain.doFilter(request, response),也就是调用ProxiedFilterChain的doFilter方法。
ProxiedFilterChain# doFilter
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:
//我们已经到达了包裹链的末端,因此调用原始链
this.orig.doFilter(request, response);
} else {
this.filters.get(this.index++).doFilter(request, response, this);
}
}
}
从filters的第一个filter开始执行doFilter。执行完所有的filters后执行原始的filterChain(下面会说明这一点)。因为这里的filter都是AdviceFilter的子类。所以会调用AdviceFilter的doFilterInternal方法。如下:
boolean continueChain = preHandle(request, response);
if (continueChain) {
executeChain(request, response, chain);
}
postHandle(request, response);protected void executeChain(ServletRequest request, ServletResponse response, FilterChain chain) throws Exception { chain.doFilter(request, response); }
上面在讲AdviceFilter时没有展开说明,这里具体说明下。我们以ProxiedFilterChain中的filters有两个filter为例说明。A过滤器和B过滤器。(注意ProxiedFilterChain的doFilter是将自己传到doFilter中,所以ProxiedFilterChain中的filter后续调用的 chain.doFilter(request, response)就会再次调用ProxiedFilterChain的doFilter)
第一种情况:如果A.doFilterInternal的preHandle返回true,则执行executeChain。那么就会执行B.doFilterInternal。如果preHandle也返回true,则继续执行executeChain。这时就会触发ProxiedFilterChain#doFilter方法中低第一个if条件,执行原始的filterChain的doFilter方法。
第二种情况:如果A.doFilterInternal的preHandle返回false,会发现不能够再执行B过滤器,也就执行不了原始的filterChain的doFilter方法。(因为不能够调用原始的filterChain的doFilter方法,而servlet.service(request, response)执行必须是最后一个filter处理完后调用filterChain的doFilter。(从org.apache.catalina.core.ApplicationFilterChain的doFilter源码中可以很明显的看出这一点),所以也就不能够触发请求的接口)。在这里就是SpringShiroFilter执行完却没有调用原始的filterChain的doFilter方法。即使SpringShiroFilter是过滤器链上最后一个filter。
图示