您的位置:首页 > 其它

Shiro源码研究之处理一次完整的请求

2017-12-14 09:36 330 查看
经过上一篇博客里的讨论,我们知道了最终被并入到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

4000
// 这个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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  filter servlet 源码 shiro