shiro过滤器链和web容器过滤器链(1)

首先看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方法,这样过滤器链再获取下一个过滤器,不断重复这些步骤,直到所有过滤器执行完

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(看下一篇)

猜你喜欢

转载自blog.csdn.net/weixin_41490593/article/details/90055562