shiro源码解析-doFilter
- 2019 年 10 月 30 日
- 笔记
shiro应该算的上java中最流行的权限框架了,使用的多了,便想着研究一下源码,看它究竟怎么运行的。
doFilter是shiro对于每个请求都会走的一个效验过程。它的流程如下
从DelegatingFilterProxy
开始,执行dofilter(),这里是一个代理模式,执行的是WebApplicationContext
中的filter执行的dofilter
方法,这个filter就是shiroFilter
.从OncePerRequestFilter
开始跟踪,它的执行dofilter()
。在dofilter()
中,只有这个一个判断,就是已经执行过shiro filter的请求不会再去执行shiro filter,实现方式是在http attribute中set一个属性(filter类名.FILTERED),即shiroFilter.FILTERED
。接着执行invokeDelegate
调用代理对象的dofilter()
方法
@Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException { // Lazily initialize the delegate if necessary. Filter delegateToUse = this.delegate; if (delegateToUse == null) { synchronized (this.delegateMonitor) { delegateToUse = this.delegate; if (delegateToUse == null) { WebApplicationContext wac = findWebApplicationContext(); if (wac == null) { throw new IllegalStateException("No WebApplicationContext found: " + "no ContextLoaderListener or DispatcherServlet registered?"); } delegateToUse = initDelegate(wac); } this.delegate = delegateToUse; } } // 调用代理对象的dofilter方法 invokeDelegate(delegateToUse, request, response, filterChain); }
protected void invokeDelegate( Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException { delegate.doFilter(request, response, filterChain); }
delegate
filter的跟踪从AbstractShiroFilter
这个父类开始,它是OncePerRequestFilter
的子类,OncePerRequestFilter
会调用doFilterInternal
抽象方法,AbstractShiroFilter
去实现它。在doFilterInternal
方法中,它会包装request,response,接着将包装后的request,response处理,使用的ServletRequestWrapper
,ServletResponseWrapper
,但是包装对象还是原始的request和response,没有替代。这么做的原因应该是便于自定义扩展,比如做一些针对流的操作,流不可重复读写,而采用其他对象包装后,可以把读写后的内容到另一个流里,再去返回。接着生成一个请求对应Subject,让这个Subject去执行新线程的executeChain
执行链。
为什么不用线程池去管理线程,而要采用新建线程这种粗暴的方式执行filterChain链,难道Subject的数量是限制了? 在源码中,线程并没有做大小的限制,subject也没有做数量大小的限制,来一个请求就新增一个subject。这里我们采用的方式是重写SubjectFactory,通过redis去缓存principals,根据缓存principals,判断缓存authenticated,通过上面的参数,new Subject。继承Subject,加上user属性,缓存user。
在executeChain()
方法中,它会在根据请求url,在与FilterChainManager
中所有的shiroFilter的chainName(定义的url名)比较,如果存在匹配,这返回对应的filterList。默认使用LinkedHashMap
去存url与filterList。并将匹配filter
包装到一个FilterChain
对象中。接着执行FilterChain
。
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; } });
ProxiedFilterChain
是FilterChain的shiro实现类,用于filters链的实现,实现方式很巧妙,在chain执行一个filter,并自增下标,在filter.doFilter()中又调用chain.doFilter,实现链的接下来filter的调用。这个做法是不怎么标准的“责任链模式”。
public class ProxiedFilterChain implements FilterChain { private static final Logger log = LoggerFactory.getLogger(ProxiedFilterChain.class); private FilterChain orig; //web.xml配置的filter private List<Filter> filters; //shiro的filter private int index = 0; //执行到了哪一个filter public ProxiedFilterChain(FilterChain orig, List<Filter> filters) { // init property } public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { if (this.filters == null || this.filters.size() == this.index) { this.orig.doFilter(request, response); } else { this.filters.get(this.index++).doFilter(request, response, this); } } }
这次执行的就是匹配filter执行dofilter,我们这里匹配的filter是AuthenticatingFilter
下的子类。它会先走AdviceFilter
的doFilterInternal()
。在doFilterInternal()
,依次调用preHandle
,executeChain
,postHandle
三个方法,就好像AOP的切面中的执行前,执行,执行后三个方法。preHandle就是这个filter验证的逻辑,executeChain将调用chain.dofilter()
,继续执行下一个filter,postHandle()是递归回来后,去执行什么操作,默认不执行任何操作。这样执行链chain
中所有filter,设计精巧,脑洞清奇。如果某个filter失效了,直接在preHandle()中用webTool.write写返回,或默认跳转页面。

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); } }
调用preHandle会先进入AccessControlFilter
重写的preHandle
方法,它会判断允许通过处理或禁止通过处理。用||
判断,所以isAccessAllowed
通过了就不会再走onAccessDenied()
。
这里有一个设计的bug,就是访问login接口的时候带正确
token
,它在isAccessAllowed
方法中返回true
,不会进入onAccessDenied
。而生成并返回新token的处理一般就是在onAccessDenied
中,它复杂未通过效验的处理。 解决方法是,重写isAccessAllowed
方法,在方法中加上isLoginUrl()
的判断,如果是loginUrl
,返回false,让它走isAccessAllowed
。
public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue); }
isAccessAllowed方法
@Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { return super.isAccessAllowed(request, response, mappedValue) || (!isLoginRequest(request, response) && isPermissive(mappedValue)); }
AuthorizationFilter的onAccessDenied方法
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException { Subject subject = getSubject(request, response); // If the subject isn't identified, redirect to login URL if (subject.getPrincipal() == null) { saveRequestAndRedirectToLogin(request, response); } else { // If subject is known but not authorized, redirect to the unauthorized URL if there is one // If no unauthorized URL is specified, just return an unauthorized HTTP status code String unauthorizedUrl = getUnauthorizedUrl(); //SHIRO-142 - ensure that redirect _or_ error code occurs - both cannot happen due to response commit: if (StringUtils.hasText(unauthorizedUrl)) { WebUtils.issueRedirect(request, response, unauthorizedUrl); } else { WebUtils.toHttp(response).sendError(HttpServletResponse.SC_UNAUTHORIZED); } } return false; }
这是子AuthorizationFilter的onAccessDenied
Subject subject = getSubject(request, response); // If the subject isn't identified, redirect to login URL if (subject.getPrincipal() == null) { saveRequestAndRedirectToLogin(request, response); } else { // If subject is known but not authorized, redirect to the unauthorized URL if there is one // If no unauthorized URL is specified, just return an unauthorized HTTP status code String unauthorizedUrl = getUnauthorizedUrl(); //SHIRO-142 - ensure that redirect _or_ error code occurs - both cannot happen due to response commit: if (StringUtils.hasText(unauthorizedUrl)) { WebUtils.issueRedirect(request, response, unauthorizedUrl); } else { WebUtils.toHttp(response).sendError(HttpServletResponse.SC_UNAUTHORIZED); } }
实际上在我们项目中,只有一个filter执行,在preHandle()
方法中验证是否通过效验,如果没有通过,使用调用login接口,接着直接webTool.write
,返回response,结束请求。
如果return ture
,它接着执行filter,并由tomcat的filter WsFilter
,进入程序接口。
dofilter流程图

注意 spring的filter源码也是这么处理的,从
OncePerRequestFilter
开始,做法一致。
总体来说,shiro的dofilter流程如下。首先进入shiro的主filter,这个filter的作用是获得该url与shiro适配的filters,并把这个适配的List<filter>
放入filterChain中去继续执行。接着执行filteChain中的下一个filter,这个filter是FilterChain
中的,它是shiro中筛选url的filter。这些filter都继承AdviceFilter
,去执行doFilterInternal()
,doFilterInternal()
依次执行preHandle
,executeChain
,postHandle
三个方法,分别是判断是否执行(具体filter的业务就是重写preHandle
,),执行链中的下一个filter,filter执行结束后的操作。AdviceFilter
有的executeChain会调用下一个filter,从而实现所有filter的效验。AccessControlFilter