首先看shiro过滤器的UML结构
shiro提供的内置过滤器(11种)很多都继承AccessControlFilter,包括自定义过滤器也可继承
以下开始分析
1.tomcat的过滤器是从哪里开始执行的?
首先明确servlet规范中的Filter和FilterChain,tomcat的过滤器链实现FilterChian
tomcat的过滤器链为ApplicationFilterChain
一个请求到tomcat后,到过滤器链中开始执行
tomcat中,过滤器开始执行的地方就是在过滤器链中ApplicationFilterChain的doFilter方法开始
跟踪代码知道,过滤器链执行就是把自己包含的所有过滤器都一个一个取出来,然后执行
filter.doFilter(request, response, this);
并且把自己作为参数传递到第一个过滤器中,如果第一个过滤器处理完毕要放行给下一个过滤器执行,就可以再拿到过滤器链ApplicationFilterChain.doFilter方法,这样过滤器链再获取下一个过滤器,不断重复这些步骤,直到所有过滤器执行完
最后调用servlet
this.servlet.service(request, response);
过滤器链有句代码设置servlet进去
void setServlet(Servlet servlet) {
this.servlet = servlet;
}
2.明白tomcat过滤器怎么执行后,那么web应用中如果用到了shiro的过滤器,那么shiro的过滤器是怎么执行的?
场景:使用spingboot+shiro结合
看下代码
/**
* Shiro过滤器配置
*/
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager)
{
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// Shiro的核心安全接口,这个属性是必须的
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 身份认证失败,则跳转到登录页面的配置
shiroFilterFactoryBean.setLoginUrl(loginUrl);
// 权限认证失败,则跳转到指定页面
shiroFilterFactoryBean.setUnauthorizedUrl(unauthorizedUrl);
// Shiro连接约束配置,即过滤链的定义
LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
// 对静态资源设置匿名访问
filterChainDefinitionMap.put("/favicon.ico**", "anon");
filterChainDefinitionMap.put("/numberone.png**", "anon");
filterChainDefinitionMap.put("/css/**", "anon");
filterChainDefinitionMap.put("/docs/**", "anon");
filterChainDefinitionMap.put("/fonts/**", "anon");
filterChainDefinitionMap.put("/img/**", "anon");
filterChainDefinitionMap.put("/ajax/**", "anon");
filterChainDefinitionMap.put("/js/**", "anon");
filterChainDefinitionMap.put("/numberone/**", "anon");
filterChainDefinitionMap.put("/druid/**", "anon");
filterChainDefinitionMap.put("/captcha/captchaImage**", "anon");
// 退出 logout地址,shiro去清除session
filterChainDefinitionMap.put("/logout", "logout");
// 不需要拦截的访问
filterChainDefinitionMap.put("/login", "anon,captchaValidate");
// 系统权限列表
// filterChainDefinitionMap.putAll(SpringUtils.getBean(IMenuService.class).selectPermsAll());
Map<String, Filter> filters = new LinkedHashMap<>();
filters.put("onlineSession", onlineSessionFilter());
filters.put("syncOnlineSession", syncOnlineSessionFilter());
filters.put("captchaValidate", captchaValidateFilter());
// 注销成功,则跳转到指定页面
filters.put("logout", logoutFilter());
shiroFilterFactoryBean.setFilters(filters);
// 所有请求需要认证
filterChainDefinitionMap.put("/**", "user,onlineSession,syncOnlineSession");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
上面的代码就说明,在shiro中,配置自定义过滤器和过滤器链定义
注意说明一下:
这是定义了一个shiro的过滤器链
filterChainDefinitionMap.put("/logout", "logout");
这定义了一个shiro过滤器链,但是链中有两个过滤器
filterChainDefinitionMap.put("/login", "anon,captchaValidate");
还要注意:貌似tomcat只有一个过滤器链,就是ApplicationFilterChain,而shiro可以定义的过滤器链有很多
(1)怎么体现tomcat过滤器链和shiro过滤器链结合?
ShiroFilterFactoryBean 是一个工厂bean,即实现了spring提供的FactoryBean
spring应用启动时,会调用其getObject()方法,查看代码可知返回的是shiro的过滤器AbstractShiroFilter.
好,知道上面解释后,debug一个请求到tomcat过滤器链看看
debug到这个位置后,找到ApplicationFilterChain中的属性
光标放在filters属性上,可看到
SpringShiroFilter是ShiroFilterFactoryBean内部类,也是继承AbstractShiroFilter,就是说spring调用ShiroFilterFactoryBean的getObject方法,返回AbstractShiroFilter后,把AbstractShiroFilter的实现加入到了tomcat过滤器链中,成为链上的一位成员.过滤器名称是shiroFilterFactoryBean
注意:这位成员很特殊
上面的debug中,是tomcat的过滤器链ApplicationFilterChain取过滤器执行
当取出过滤器shiroFilterFactoryBean时,接着调用其doFilter方法
然后进入如下代码
看看UML,因为AbstractShiroFilter是继承OncePerRequestFilter,所以进入其doFilter方法
看上面红色部分,是tomcat的过滤器链
好,到了这里,接下来又该怎么分析?
看这句代码
this.doFilterInternal(request, response, filterChain);
doFilterInternal方法是给OncePerRequestFilter子类实现的,那直接子类是谁?
有两个,一个AbstractShiroFilter,一个是AdviceFilter
那进哪一个?当然是AbstractShiroFilter
那就分析doFilterInternal(request, response, filterChain);方法
protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain) throws ServletException, IOException {
Throwable t = null;
try {
final ServletRequest request = this.prepareServletRequest(servletRequest, servletResponse, chain);
final ServletResponse response = this.prepareServletResponse(request, servletResponse, chain);
Subject subject = this.createSubject(request, response);
subject.execute(new Callable() {
public Object call() throws Exception {
AbstractShiroFilter.this.updateSessionLastAccessTime(request, response);
AbstractShiroFilter.this.executeChain(request, response, chain);
return null;
}
});
} catch (ExecutionException var8) {
t = var8.getCause();
} catch (Throwable var9) {
t = var9;
}
if (t != null) {
if (t instanceof ServletException) {
throw (ServletException)t;
} else if (t instanceof IOException) {
throw (IOException)t;
} else {
String msg = "Filtered request failed.";
throw new ServletException(msg, t);
}
}
}
从上面的代码,知道有什么?
<1>shiro将tomcat的request和response包装成自己的ShiroHttpServletRequest和ShiroHttpServletResponse,从这以后的代码中,获取的request和response就是shiro的ShiroHttpServletRequest和ShiroHttpServletResponse
这里留一个疑问?
那如果在后面代码中,除了Controller中可以获取request和response,那其他地方通过spring提供的工具获取的request和response是属于shiro包装后的么?如下:
public static ServletRequestAttributes getRequestAttributes()
{
RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
return (ServletRequestAttributes) attributes;
}
/**
* 获取request
*/
public static HttpServletRequest getRequest()
{
return getRequestAttributes().getRequest();
}
<2>知道shiro的subject 是在AbstractShiroFilter过滤器中创建的
<3>重点分析下面截取的代码
subject.execute(new Callable() {
public Object call() throws Exception {
AbstractShiroFilter.this.updateSessionLastAccessTime(request, response);
AbstractShiroFilter.this.executeChain(request, response, chain);
return null;
}
});
如果已经有了session,则在这里更新session的最新访问时间
AbstractShiroFilter.this.updateSessionLastAccessTime(request, response);
下面重点:
AbstractShiroFilter.this.executeChain(request, response, chain);
这句代码就是说明shiro的过滤器链从这里开始执行
等等,这时的chain还是tomcat的过滤器链ApplicationFilterChain,那怎么就是shiro过滤器链开始执行了?
进入executeChain()方法
protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain) throws IOException, ServletException {
FilterChain chain = this.getExecutionChain(request, response, origChain);
chain.doFilter(request, response);
}
再进入getExecutionChain()方法
protected FilterChain getExecutionChain(ServletRequest request, ServletResponse response, FilterChain origChain) {
FilterChain chain = origChain;
FilterChainResolver resolver = this.getFilterChainResolver();
if (resolver == null) {
log.debug("No FilterChainResolver configured. Returning original FilterChain.");
return origChain;
} else {
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;
}
}
重点就是FilterChain resolved = resolver.getChain(request, response, origChain);这句
这句代码就是把tomcat的过滤器链包装
怎么包装?
先看FilterChainResolver ,这是一个接口,只有一个方法
public interface FilterChainResolver {
FilterChain getChain(ServletRequest var1, ServletResponse var2, FilterChain var3);
}
而这接口的实现是PathMatchingFilterChainResolver
public class PathMatchingFilterChainResolver implements FilterChainResolver
这个FilterChainResolver怎么包装tomcat的过滤器链?
先看它实现类PathMatchingFilterChainResolver 的代码
public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) {
FilterChainManager filterChainManager = this.getFilterChainManager();
if (!filterChainManager.hasChains()) {
return null;
} else {
String requestURI = this.getPathWithinApplication(request);
Iterator var6 = filterChainManager.getChainNames().iterator();
String pathPattern;
do {
if (!var6.hasNext()) {
return null;
}
pathPattern = (String)var6.next();
} while(!this.pathMatches(pathPattern, requestURI));
if (log.isTraceEnabled()) {
log.trace("Matched path pattern [" + pathPattern + "] for requestURI [" + requestURI + "]. " + "Utilizing corresponding filter chain...");
}
return filterChainManager.proxy(originalChain, pathPattern);
}
}
以上代码作用
1.先是取出shiro过滤器链的管理器,判断当前shiro是否有过滤器链,就是文章开头里的配置shiro的过滤链,如 filterChainDefinitionMap.put("/login", “anon,captchaValidate”);
shiro过滤器链管理器,是shiro启动时就创建的
public class DefaultFilterChainManager implements FilterChainManager
在它的实现中,有类型为map的filterChains属性,key是uri,value是这个uri的过滤器集合
map中的一个映射就是过滤器链
private Map<String, NamedFilterList> filterChains = new LinkedHashMap();
过滤器链的顺序和ShiroFilterFactoryBean中配置的一致
2.取出当前请求的uri,然后循环从DefaultFilterChainManager 的fiterChains中取出一个个映射(过滤器链)的uri,判断是否匹配
3.如果有匹配上了就进入DefaultFilterChainManager proxy方法,把tomcat过滤器链和shiro匹配的过滤器链名称(即uri作为名称)作为参数传递
return filterChainManager.proxy(originalChain, pathPattern);
public FilterChain proxy(FilterChain original, String chainName) {
NamedFilterList configured = this.getChain(chainName);
if (configured == null) {
String msg = "There is no configured chain under the name/key [" + chainName + "].";
throw new IllegalArgumentException(msg);
} else {
return configured.proxy(original);
}
}
然后再看configured.proxy(original);这句代码,是进入到SimpleNamedFilterList 中
proxy方法
public class SimpleNamedFilterList implements NamedFilterList
public FilterChain proxy(FilterChain orig) {
return new ProxiedFilterChain(orig, this);
}
就是proxy方法中,为当前的请求匹配的uri,创建一个代理过滤器链ProxiedFilterChain
创建一个ProxiedFilterChain就包含tomcat过滤器链和uri对应配置的过滤器集合
这样一个ProxiedFilterChain就是一个shiro过滤器链
看看ProxiedFilterChain类
public class ProxiedFilterChain implements FilterChain {
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.");
} else {
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) {
if (log.isTraceEnabled()) {
log.trace("Invoking wrapped filter at index [" + this.index + "]");
}
((Filter)this.filters.get(this.index++)).doFilter(request, response, this);//真正开始执行shiro过滤器
} else {
if (log.isTraceEnabled()) {
log.trace("Invoking original filter chain.");
}
this.orig.doFilter(request, response);
}
}
}
这类使用了设计模式中的装饰模式,继承servlet过滤器链,还包含自身,这样做就可以包含tomcat的过滤器链,等执行完shiro中的过滤器链中的过滤器后,再回到tomcat过滤器链中执行剩下没有执行的tomcat过滤器
这里注意一个重要问题
每一次请求到tomcat后,首先在tomcat的过滤器链中执行完前几个过滤器后,进入到tomcat过滤器链中的ShiroFilterFactoryBean时,就是进入到了从shiro启动时就创建的众多过滤器链中,找到一个匹配的,就创建一个ProxiedFilterChain,这个代理过滤器链就代表当前shiro的过滤器链.
如果执行完tomcat过滤器链中的ShiroFilterFactoryBean自身中包含的shiro过滤器链时,会回到tomcat过滤器链中ShiroFilterFactoryBean的下一个tomcat过滤器
好,理解了上面这段话后,看看下面UML(看下一篇)