经过上一篇博客里的讨论,我们知道了最终被并入到Servlet的FilterChain中的Filter实例为
ShiroFilterFactoryBean.SpringShiroFilter
类型。而这篇博客就让我们来看看Shiro是如何借助这个Filter来完成权限校验这个庞大功能的?
1. ShiroFilterFactoryBean.SpringShiroFilter
类
现在让我们来看看ShiroFilterFactoryBean.SpringShiroFilter
类的类定义。
// ShiroFilterFactoryBean.SpringShiroFilter
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);
}
}
}
代码很简单,只有securityManager
和filterChainResolver
字段的设置,因此主要逻辑肯定就在其基类AbstractShiroFilter
中了。
2. AbstractShiroFilter.doFilterInternal
类
作为一个Filter,其核心方法当然就是doFilter
了。
protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain)
throws ServletException, IOException {
Throwable t = null;
try {
// 将接收到的servletRequest,servletResponse使用Wrapper模式封装为自定义的ShiroHttpServletRequest, ShiroHttpServletResponse
final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);
final ServletResponse response = prepareServletResponse(request, servletResponse, chain);
// 构建一个Subject
// 这里的subject的实际类型是WebDelegatingSubject
// 详细解释参见本人的另外一篇博客: http://blog.csdn.net/lqzkcx3/article/details/78801403
final Subject subject = createSubject(request, response);
//noinspection unchecked
// 这个execute方法实际被定义在DelegatingSubject类中
// 这个execute将方法的调用者subject存入到当前Thread的线程本地存储中,也就是将该Subject实例与当前Thread进行绑定。具体细节参见本人的另一篇博客http://blog.csdn.net/lqzkcx3/article/details/78801403的第6节。
// 那样在我们调用`SecurityUtils.getSubject()`就能直接获取到该Subject实例了。
subject.execute(new Callable() {
public Object call() throws Exception {
// 更新最后读取session的时间
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);
}
}
3. AbstractShiroFilter.executeChain
方法
处理请求时的核心方法
// AbstractShiroFilter.executeChain
protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain)
throws IOException, ServletException {
FilterChain chain = getExecutionChain(request, response, origChain);
chain.doFilter(request, response);
}
4. AbstractShiroFilter.getExecutionChain
方法
// AbstractShiroFilter.getExecutionChain
protected FilterChain getExecutionChain(ServletRequest request, ServletResponse response, FilterChain origChain) {
FilterChain chain = origChain;
//上一篇文章提到了,filterChainResolver字段被硬编码为PathMatchingFilterChainResolver;当然我们是可以自定义,不过很少有这个必要。
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;
}
5. PathMatchingFilterChainResolver.getChain
方法
// PathMatchingFilterChainResolver.getChain
public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) {
// DefaultFilterChainManager
FilterChainManager filterChainManager = getFilterChainManager();
if (!filterChainManager.hasChains()) {
return null;
}
// 以 http://www.somehost.com/myapp/my/url.jsp举例的话;这里返回的requestURI, 其值为 /my/url.jsp
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
/* 这里我们以上一篇博客的例子里的配置来进行讲解
/login.jsp = authc
/logout = logout
/unauthorized.jsp = anon
/main** = authc
/admin/list** = authc,perms[admin:manage]
/user/info-anon** = anon
/user/info** = authc
/** = anon
每次迭代的pathPattern就是上面的"/login.jsp", "/logout","/unauthorized.jsp"等等
*/
// 一个pathPattern可能被配置多个Filter, 而shiro采用了继承自List<Filter>的NamedFilterList接口来存储这些Filter
// 所以DefaultFilterChainManager类中有一个Map<String, NamedFilterList>类型的filterChains字段, 其中存放的正式上面用户配置的关系
// NamedFilterList接口到1.3.2为止, 唯一的实现依然是SimpleNamedFilterList
// DefaultFilterChainManager类中另外一个Map<String, Filter>类型的字段 filters ,让我们看看其内部的存储:
// {anon=anon, authc=authc, authcBasic=authcBasic, logout=logout, noSessionCreation=noSessionCreation, perms=perms, port=port, rest=rest, roles=roles, ssl=ssl, user=user, xxxxModuleAuthc=xxxxModuleAuthc, upmsFormAuthenticationFilter=upmsFormAuthenticationFilter, upmsSessionForceLogout=upmsSessionForceLogout}
// 所以它里面存储的是默认的Filter + 我们自定义注册的Filter
// 筛选出与当前请求链接匹配的FilterChain
for (String pathPattern : filterChainManager.getChainNames()) {
// If the path does match, then pass on to the subclass implementation for specific checks:
// 这里默认的匹配模式为org.apache.shiro.util.AntPathMatcher。
if (pathMatches(pathPattern, requestURI)) {
// 一旦匹配, 则对Servlet的FilterChain进行代理。保证Shiro自定义的Filter执行完毕之后,再执行Servlet的Filter。
// 下文进行细节讲解
return filterChainManager.proxy(originalChain, pathPattern);
}
}
return null;
}
6. DefaultFilterChainManager.proxy
方法
public FilterChain proxy(FilterChain original, String chainName) {
// 从上一小节里讲解的filterChains字段中取出对应的NamedFilterList
NamedFilterList configured = getChain(chainName);
if (configured == null) {
String msg = "There is no configured chain under the name/key [" + chainName + "].";
throw new IllegalArgumentException(msg);
}
// proxy实现是方法的实现如下 :
// return new ProxiedFilterChain(orig, this);
return configured.proxy(original);
}
7. ProxiedFilterChain
类
ProxiedFilterChain
类 实现了Servlet规范中的FilterChain
接口,
// ProxiedFilterChain.doFilter
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
// Shiro自身的Filter执行完毕之后; 开始执行Servlet源生的Filter
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 + "]");
}
// 执行Shiro自身定义的Filter。
// 关于filters字段在上一小节中已经谈及过;更具体的还请参见下一篇博客。Shiro针对Filter的继承链还是比较深的,值得专门写一篇。
this.filters.get(this.index++).doFilter(request, response, this);
}
}
8. 总结
- 相比较本人之前对
Filter
的扩展,一直考虑的都是向FilterChain
中插入自定义Filter
; Shiro则直接来了个更狠的,直接将整个FilterChain
代理掉,先执行完我的FilterChain
,才考虑Servlet
的。 - Shiro 对Servlet 容器的FilterChain 进行了代理,即ShiroFilter 在继续Servlet 容器的Filter链的执行之前,通过ProxiedFilterChain`对Servlet 容器的FilterChain 进行了代理;即先走Shiro 自己的Filter 体系,然后才会委托给Servlet 容器的FilterChain 进行Servlet 容器级别的Filter链执行; 《跟开涛学Shiro》P77
- 每次请求的到来,Shiro都会从我们配置到
org.apache.shiro.spring.web.ShiroFilterFactoryBean
的filterChainDefinitions
中挑选一个匹配过滤链(多个匹配也只会选择第一个匹配的),Shiro会执行这个链条,最后采取执行其他Servlet Filter。