在配置shiro的时候第一件事情就是在web.xml文件中配置一个由spring提供的类:org.springframework.web.filter.DelegatingFilterProxy 按照字面的翻译这应该是一个代理过滤器的策略。
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<async-supported>true</async-supported>
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
</filter-mapping>
这个类其实是将上下文中名称为shiroFilter的类做成一个代理过滤器。该类将从spring上下文中找到自己要代理的过滤器类,并负责初始化和销毁该过滤器。并在每次发起的拦截请求是先走该类的方法invokeDelegate()。
##初始化
protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
Filter delegate = wac.getBean(getTargetBeanName(), Filter.class);
if (isTargetFilterLifecycle()) {
delegate.init(getFilterConfig());
}
return delegate;
}
初始化过程就是将配置文件中的参数注入到ShiroFilterFactoryBean中的SpringShiroFilter中。
##请求调用 当我们在浏览器中打印了一个能够被shiro拦截的uri的时候首先会进入到DelegatingFilterProxy的doFilter方法中。该方法首先判断被代理的拦截器是否被初始化,如果没有则实行懒加载策略初始化shiro拦截器。否则则开始调用shiro拦截器去执行拦截。
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) {
if (this.delegate == null) {
WebApplicationContext wac = findWebApplicationContext();
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: no ContextLoaderListener registered?");
}
this.delegate = initDelegate(wac);
}
delegateToUse = this.delegate;
}
}
invokeDelegate(delegateToUse, request, response, filterChain);
}
protected void invokeDelegate(
Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// 调用真正shirofilter
delegate.doFilter(request, response, filterChain);
}
delegate实例最终类型是SpringShiroFilter。该类集成在AbstractShiroFilter。
private static final class SpringShiroFilter extends AbstractShiroFilter {
protected SpringShiroFilter(WebSecurityManager webSecurityManager, FilterChainResolver resolver) {
super();
if (webSecurityManager == null) {
throw new IllegalArgumentException("WebSecurityManager property cannot be null.");
}
setSecurityManager(webSecurityManager);
if (resolver != null) {
setFilterChainResolver(resolver);
}
}
}
AbstractShiroFilter类继承自OncePerRequestFilter
public abstract class AbstractShiroFilter extends OncePerRequestFilter{}
所以我们首先来看看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 //不过滤被配置不过滤的请求
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 {
//将来自该过滤请求(如shiroFitler)设置成已经被过滤
log.trace("Filter '{}' not yet executed. Executing now.", getName());
request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
try {
//执行shiro的过滤逻辑。
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);
}
}
}
如果到达的请求没有被OncePerRequestFilter过滤掉,则会走shiro的拦截请求。值得一起的是,FilterChain chain其实是spring的servlet上下文。这个是为了包装request、response的时候将servlet上下文也放置进来。
protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain)
throws ServletException, IOException {
Throwable t = null;
try {
final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);//包装request
final ServletResponse response = prepareServletResponse(request, servletResponse, chain);//包装response
final Subject subject = createSubject(request, response);//创建subject对象
//执行回调函数。
//1、更新session的最后访问时间
//2、执行拦截器链。
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;
}
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);
}
}
shiro的拦截器首先将request和response进行了包装。并创建subject执行回调函数。该函数主要执行了两个部分:1、更新session的最后访问时间 2、执行拦截器链。重点看下执行拦截器链逻辑。
protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain)
throws IOException, ServletException {
FilterChain chain = getExecutionChain(request, response, origChain);//获得拦截器链
chain.doFilter(request, response);//执行拦截器链
}
拦截器逻辑主要分成了两个部分:1、获得拦截器链 2、执行拦截器链。 1、获得拦截器链
protected FilterChain getExecutionChain(ServletRequest request, ServletResponse response, FilterChain origChain) {
FilterChain chain = origChain;
//1.1获得拦截器链解析器
FilterChainResolver resolver = getFilterChainResolver();
if (resolver == null) {
log.debug("No FilterChainResolver configured. Returning original FilterChain.");
return origChain;
}
//1.2获得最终的拦截器执行链。
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;
}
1.1、获得拦截器链解析器最终获得了PathMatchingFilterChainResolver的实例。 1.2、获得最终的拦截器执行链是执行PathMatchingFilterChainResolver中的getChain方法。
public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) {
//获得过滤链管理器。该管理器是在初始化SpringShiroFilter时候注入进来的。
FilterChainManager filterChainManager = getFilterChainManager();
if (!filterChainManager.hasChains()) {
return null;
}
//获得请求uri
String requestURI = getPathWithinApplication(request);
//uri和拦截器链做匹配。
for (String pathPattern : filterChainManager.getChainNames()) {
//如果匹配成功
if (pathMatches(pathPattern, requestURI)) {
//1.3返回一个代理过滤器
return filterChainManager.proxy(originalChain, pathPattern);
}
}
return null;
}
```
1.3是将当前的servlet上下文和请求uri一起包装成为一个过滤器代理。
```java
FilterChainManager类
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);
}
public class SimpleNamedFilterList implements NamedFilterList {
public FilterChain proxy(FilterChain orig) {
return new ProxiedFilterChain(orig, this);
}
}
2、执行拦截器链 刚才看到获得拦截器的过程其实就是生成了一个类型为ProxiedFilterChain的实例。那么执行自然就是该类的doFilter方法。
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
//如果过滤器链为空,则执行servlet上下文的过滤器。
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);
}
}
这里面涵盖了一个过滤器的设计模式。当我们给某个uri设计多个拦截器的时候,这几个拦截器是怎么连续执行的呢?换句话说上面的index是如何一个一个累加直到this.filters.size() == this.index条件成立跳出循环的呢?我们来看下shiro里面的拦截器链实现。 首先观察一下shiro配置文章的这个段
/static/** = anon
/js/** =anon
/css/** =anon
/favicon.ico =anon
/images/** = anon
/logout = logout
/user/login=authc
/** =sysUser,onlineSession,syncOnlineSession,perms,roles
我们不难发现这里面的拦截器都扩展自接口AdviceFilter或接口AdviceFilter的实现。给接口作用就是在执行拦截器前后添加一些逻辑。
public abstract class AdviceFilter extends OncePerRequestFilter {
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
return true;
}
protected void postHandle(ServletRequest request, ServletResponse response) throws Exception {
}
public void afterCompletion(ServletRequest request, ServletResponse response, Exception exception) throws Exception {
}
protected void executeChain(ServletRequest request, ServletResponse response, FilterChain chain) throws Exception {
chain.doFilter(request, response);
}
//执行逻辑
public void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
throws ServletException, IOException {
Exception exception = null;
try {
//执行前的逻辑,如果未返回true则不执行过滤器逻辑。
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);
}
}
protected void cleanup(ServletRequest request, ServletResponse response, Exception existing)
throws ServletException, IOException {
}
}
AdviceFilter扩展自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方法是执行本过滤器的过滤逻辑。执行filterChain是执行过利器链里面下一个过滤器的执行逻辑。filterChain的类型都是ProxiedFilterChain。
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.");
}
//如果过滤器为空,或者过滤器链已经执行完,则orig里面如果有过滤器逻辑则执行。
this.orig.doFilter(request, response);
} else {
if (log.isTraceEnabled()) {
log.trace("Invoking wrapped filter at index [" + this.index + "]");
}
//获得第index个过滤器并执行。
this.filters.get(this.index++).doFilter(request, response, this);
}
}
过滤器模式简单地说就是:到了某个过滤器他会执行自己的逻辑(doFilterInternal方法),执行完自己的逻辑之后,他还会到刚刚传过来的过滤器链的doFilter方法返回到过滤器链下一个过滤器继续执行。 这里也有个疑惑,就是过滤器链执行完毕之后应该怎么返回到springmvc的DispatcherServlet里面的service()方法继续spring的逻辑呢? 答案就在这行: this.orig.doFilter(request, response); 我们在配置到web.xml文件中的过滤器和servlet的执行顺序首先按照配置的先后顺序执行过滤器,当过滤器执行完毕的时候会调用 FilterChain的doFilter()方法返回到servlet环境,然后再原路返回。那么为什么orig.doFilter能够返回到servlet呢,查看源码filterChian既不是servlet的代理也不是servlet的包装器。那什么时机让orig成为了servlet返回路径的呢? dubug到这里我们发现了如图的类:ServletHandler。原来这个ServletHandler是jetty用于管理Filter、FilterMapping、Servlet、ServletMapping的容器。(我用的是jetty,本段环境为jetty环境)。以及用于实现一次请求所对应的Filter链和Servlet执行流程的类。对Servlet的框架实现中,它也被认为是Handler链的末端,因而在它的doHandle()方法中没有调用nextHandle()方法。至于具体实现就等分析jetty源码的时候阐述了。