首先推荐两篇与Shiro相关的源码分析文章:
Spring DelegatingFilterProxy 源码分析
Shiro源码分析之ShiroFilterFactoryBean
本文是对上述第两篇文章的补充:
首先是对第二篇文章中ShiroFilterFactoryBean#createFilterChainManager 分析的代码中引用方法applyGlobalPropertiesIfNecessary(filter)的代码补充:
private void applyGlobalPropertiesIfNecessary(Filter filter) {
applyLoginUrlIfNecessary(filter);
applySuccessUrlIfNecessary(filter);
applyUnauthorizedUrlIfNecessary(filter);
}
该方法内调用了三个方法,但这三个方法只是针对不同对象做了类似的事情而已。以第一个applyLoginUrlIfNecessary(filter)方法为例进行分析:
//请结合下边的XML配置来理解该代码
private void applyLoginUrlIfNecessary(Filter filter) {
//获取XML中shiroFilter对应的bean中配置的loginUrl属性的值(XML中配置的值为"/login.html")
String loginUrl = getLoginUrl();
//此处注意,filter需要为AccessControlFilter或其子类型(AccessControlFilter的继承机构见下方)
if (StringUtils.hasText(loginUrl) && (filter instanceof AccessControlFilter)) {
AccessControlFilter acFilter = (AccessControlFilter) filter;
//only apply the login url if they haven't explicitly configured one already:
//获取Filter中配置的loginUrl属性的值(下方XML中配置的值也为"/login.html")
String existingLoginUrl = acFilter.getLoginUrl();
//如果该Filter中配置的loginUrl是默认值(/login.jsp)的话,就将它设置为XML中配置的值(以配置为主)
if (AccessControlFilter.DEFAULT_LOGIN_URL.equals(existingLoginUrl)) {
acFilter.setLoginUrl(loginUrl);
}
}
}
与上边代码相关的XML配置:
<!-- 基于Form表单的身份验证过滤器 -->
<bean id="formAuthenticationFilter" class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter">
<property name="usernameParam" value="username"/>
<property name="passwordParam" value="password"/>
<property name="loginUrl" value="/login.html"/>
<property name="successUrl" value="/"/>
</bean>
<!-- Shiro的Web过滤器 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<property name="loginUrl" value="/login.html"/>
<property name="filters">
<util:map>
<entry key="authc" value-ref="formAuthenticationFilter"/>
</util:map>
</property>
<property name="filterChainDefinitions">
<value>
/assets/** = anon
/css/** = anon
/img/** = anon
/js/** = anon
/logout = anon
</value>
</property>
</bean>
AccessControlFilter继承结构图如下,从图中可以看到FormAuthenticationFilter是AccessControlFilter的子类:
从上边的分析可知,其实我们在XML中对formAuthenticationFilter中loginUrl的配置是多余的,完全可以省略,ShiroFilterFactoryBean会为我们做设置。
applySuccessUrlIfNecessary(filter)与applyUnauthorizedUrlIfNecessary(filter)两个类似,只是针对的对象不同而已,具体内容自行查看代码:
applySuccessUrlIfNecessary(filter):为AuthenticationFilter或其子类设置successUrl(默认路径为"/");
applyUnauthorizedUrlIfNecessary(filter):为AuthorizationFilter或其子类设置unauthorizedUrl(无默认路径)。
其次是对开头提到的两篇文章中的第一篇文章的补充:
这篇文章中只提到了Filter生命周期(init() -- doFilter() -- destory())中的init()生命周期,没有提到后两个,这里对doFilter()进行简单的解析(请重点关注代码中的注释),这里会涉及到web.xml中配置的Filter(代码中称为origChain,是这些Filter构成的FilterChain)与Shiro中的Filter执行的先后顺序。
先看一下DelegatingFilterProxy#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) {
if (this.delegate == null) {
WebApplicationContext wac = findWebApplicationContext();
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: " +
"no ContextLoaderListener or DispatcherServlet registered?");
}
this.delegate = initDelegate(wac);
}
delegateToUse = this.delegate;
}
}
// Let the delegate perform the actual doFilter operation.
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.doFilter()会调用OncePerRequestFilter#doFilter() => AbstractShiroFilter#doFilterInternal() => AbstractShiroFilter#executeChain()
protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain)
throws ServletException, IOException {
Throwable t = null;
try {
//prepareServletRequest()方法会将HttpServletRequest封装成ShiroHttpServletRequest对象
final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);
//prepareServletResponse()同样:HttpServletResponse => ShiroHttpServletResponse
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;
}
});
} 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);
}
}
protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain)
throws IOException, ServletException {
//这里返回的是ProxiedFilterChain实例
FilterChain chain = getExecutionChain(request, response, origChain);
//执行ProxiedFilterChain#doFilter()方法
chain.doFilter(request, response);
}
protected FilterChain getExecutionChain(ServletRequest request, ServletResponse response, FilterChain origChain) {
FilterChain chain = origChain;
FilterChainResolver resolver = getFilterChainResolver();
if (resolver == null) {
return origChain;
}
//getChain()方法会根据当前请求路径查找对应的Shiro Filter,如果没有的话会返回origChain对象
//如果有的话将origChain和Shiro Filter封装成ProxiedFilterChain对象
FilterChain resolved = resolver.getChain(request, response, origChain);
if (resolved != null) {
chain = resolved;
} else {
}
return chain;
}
PathMatchingFilterChainResolver#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:
//如果请求路径与FilterChain对应的路径匹配的话执行proxy
if (pathMatches(pathPattern, requestURI)) {
return filterChainManager.proxy(originalChain, pathPattern);
}
}
return null;
}
DefaultFilterChainManager#proxy()
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);
}
SimpleNamedFilterList#proxy()
public FilterChain proxy(FilterChain orig) {
return new ProxiedFilterChain(orig, this);
}
ProxiedFilterChain.java
/**
* ProxiedFilterChain的构造方法
* @Param orig 与web.xml中配置的Filter相对应
* @Param filters 与Shiro中的Filter相对应
*/
public ProxiedFilterChain(FilterChain orig, List<Filter> filters) {
if (orig == null) {
throw new NullPointerException("original FilterChain cannot be null.");
}
this.orig = orig;
this.filters = filters;
//index默认为0,表示Shiro的FilterChain还为执行,这个值会随着请求路径中对应的Shiro Filter的执行递增,直到所有的Shiro Filter执行完毕
this.index = 0;
}
//执行Filter的doFilter()方法
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
//如果当前请求路径中对应的Filter为空或都已经被执行完毕的话就执行web.xml中配置的Filter
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 {
//执行Shiro Filter。注意doFilter的第三个参数是this,即ProxiedFilterChain的当前对象
//这样当filters中的某个Filter的doFilter方法执行完毕以后就可以再调用PorxiedFilterChain#doFilter()了
this.filters.get(this.index++).doFilter(request, response, this);
}
}
从上边的分析可知,当web.xml中也配置了Filter的情况下,Shiro会先执行当前请求路径所对应的Shiro Filter,当这些Shiro Filter都已经执行完毕后才会执行web.xml中配置的Filter。