shiro filter在基于spring的web中的执行流程

目录

 

概述

OncePerRequestFilter

AdviceFilter

PathMatchingFilter

AccessControlFilter

基于Spring的Web应用

ShiroFilterFactoryBean

SpringShiroFilter执行流程 

图示


概述

OncePerRequestFilter

我们知道filter都是通过doFilter方法处理相关逻辑。通过源码可以发现,shiro所有的filter的doFilter方法只有OncePerRequestFilter中有。OncePerRequestFilter也是我们平时所用到的shiro的filter的抽象父类。且该方法是final的。

public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)

    public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
        if ( request.getAttribute(alreadyFilteredAttributeName) != null ) {
            filterChain.doFilter(request, response);
        } else if (!isEnabled(request, response) || shouldNotFilter(request) ) {
                filterChain.doFilter(request, response);
            } else {
                request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
                try {
                    doFilterInternal(request, response, filterChain);
                } finally {
                    request.removeAttribute(alreadyFilteredAttributeName);
                }
            }
    }
  •  alreadyFilteredAttributeName :标记该filter是否处理过请求。处理过则继续执行过滤器链上的其它filter。
  • isEnabled():如果此过滤器应该过滤指定的请求,则返回true ,如果它应该将request/response立即传递到FilterChain的下一个元素,则返回false 。默认情况下为true (以确保过滤器始终默认执行),但如有必要,它可以被子类覆盖以实现特定于请求的行为。 例如,可以根据访问的请求路径启用或禁用过滤器。
  • shouldNotFilter:已废弃。用于子类覆盖。请使用isEnabled。
  • doFilterInternal:真正的处理过滤器逻辑方法

shiro中有两个子过滤器实现了doFilterInternal方法。AdviceFilter和AbstractShiroFilter。

AdviceFilter

一个 Servlet 过滤器,它通过preHandle 、 postHandle和afterCompletion钩子为 ServletRequest 启用 AOP 风格的“环绕”建议。

    public void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
            throws ServletException, IOException {
        Exception exception = null;
        try {
            boolean continueChain = preHandle(request, response);
            if (continueChain) {
                executeChain(request, response, chain);
            }
            postHandle(request, response);
        } catch (Exception e) {
            exception = e;
        } finally {
            cleanup(request, response, exception);
        }
    }
  • preHandle:如果应该允许过滤器链继续,则返回true ,否则返回false 。 在过滤器执行链执行下一个filter之前调用它。默认返回true。(返回false,则过滤器不往下执行了,则请求的接口也就触发不了(后边详细说明),shiro的认证就在此阶段完成
  • executeChain:执行过滤器链。shiro通过调用自己的FilterChain(里边包装了原始的FilterChain)的chain.doFilter(request, response)方法,将我们通过spring配置给shiro的所有filter执行完成后,再调用原始的FilterChain的chain.doFilter(request, response)方法。后面会详细说明
  • postHandle:在目标方法(请求的接口)正常(未抛出异常)执行后完成一些操作,默认不做任何操作。
  • cleanup:在doFilterInternal实现的finally代码块中执行清理逻辑。此实现专门调用afterCompletion并正确处理任何异常。
  • afterCompletion:大多数资源“清理”行为通常在此实现中完成,即使链处理抛出异常,也保证为每个请求调用它。

这里我们重点看下子类PathMatchingFilter对于preHandle的实现

PathMatchingFilter

处理指定路径并允许所有其他路径通过。主要是对preHandle做进一步细化控制。如果是需要处理的路径,则调用isFilterChainContinued做进一步的控制。该方法中会调用onPreHandle方法。默认返回true,子类可以覆盖。

子类:

  • AnonymousFilter:onPreHandle直接返回true。这个filter就是我们配置anon不需要认证时的filter。因为直接返回true,所以不需要认证。
  • AccessControlFilter:重点说明

AccessControlFilter

控制对资源的访问的任何过滤器的超类,如果用户未经身份验证,可能会将用户重定向到登录页面。

    public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
        return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);
    }

AccessControlFilter中对onPreHandle方法做了进一步细化。通过isAccessAllowed和 onAccessDenied方法进一步细化控制。

  • isAccessAllowed:如果允许请求正常通过过滤器,则返回true ,返回false如果请求应由onAccessDenied处理。抽象方法,需要子类实现。
  • onAccessDenied:处理由isAccessAllowed方法拒绝访问的请求。此方法立即委托给onAccessDenied(ServletRequest, ServletResponse)抽象方法,需要子类实现。

AccessControlFilter后续的子类主要就是针对上述抽象方法的实现达到控制效果。

基于Spring的Web应用

ShiroFilterFactoryBean

FactoryBean在基于 Spring 的 Web 应用程序中用于定义主 Shiro 过滤器。可以添加自定义的过滤器。

getObject():通过createInstance方法懒惰地创建并返回一个AbstractShiroFilter具体实例SpringShiroFilter。

SpringShiroFilter的反向构造过程(...表示前者中持有后者。可以看出该filter中包含了shiro添加的filter。通过源码不难发现,只有添加到filterChainDefinitionMap的filter的filter定义才会被真正的执行(添加到NamedFilterList中)。所以完全可以自定义filter而不触发shiro自带的filter(shiro的DefaultFilter枚举可以找到)   

SpringShiroFilter...PathMatchingFilterChainResolver...FilterChainManager

...NamedFilterList...filters

SpringShiroFilter执行流程 

SpringShiroFilter的doFilter由servlet容器触发。文章一开头说了shiro的doFilter只有OncePerRequestFilter中有。所以看doFilterInternal真正的处理过滤器逻辑。该方法在父类AbstractShiroFilter中。

    protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain)
            throws ServletException, IOException {

        try {
            final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);
            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;
                }
            });
        } 
    }

创建了Subject,调用executeChain执行链。(重点)

   protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain)
            throws IOException, ServletException {
        FilterChain chain = getExecutionChain(request, response, origChain);
        chain.doFilter(request, response);
    }

从方法的参数命名中就可以看出,该方法的参数FilterChain命名为origChain原始的filterChain,也就是servlet提供的。而方法内部则通过原始的origChain得到了一个新的chain(文章上边所说的shiro自己的filterChain)。然后调用新的chain的doFilter方法。(当然也有可能返回的还是原始的filterChain。getExecutionChain方法一开始有个判断,自行查看,这里不考虑,因为通过SpringShiroFilter的创建过程会发现那个判断不成立)。getExecutionChain方法后续的逻辑其实就是上边SpringShiroFilter的反向构造过程的正向获取过程。最后调用的就是NamedFilterList的实现类SimpleNamedFilterList的proxy方法。NamedFilterList中有需要执行的所有filter。

    public FilterChain proxy(FilterChain orig) {
        return new ProxiedFilterChain(orig, this);
    }
  public ProxiedFilterChain(FilterChain orig, List<Filter> filters) {
        if (orig == null) {
            throw new NullPointerException("original FilterChain cannot be null.");
        }
        this.orig = orig;
        this.filters = filters;
        this.index = 0;
    }

 可以看出 ProxiedFilterChain中持有原始的filterChain.且持有所有需要执行的filter。

 

紧接上面的chain.doFilter(request, response),也就是调用ProxiedFilterChain的doFilter方法。

ProxiedFilterChain# doFilter

 public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
        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 {
            this.filters.get(this.index++).doFilter(request, response, this);
        }
    }
}

从filters的第一个filter开始执行doFilter。执行完所有的filters后执行原始的filterChain(下面会说明这一点)。因为这里的filter都是AdviceFilter的子类。所以会调用AdviceFilter的doFilterInternal方法。如下:

          boolean continueChain = preHandle(request, response);
            if (continueChain) {
                executeChain(request, response, chain);
            }
            postHandle(request, response);

protected void executeChain(ServletRequest request, ServletResponse response, FilterChain chain) throws Exception {
    chain.doFilter(request, response);
}

上面在讲AdviceFilter时没有展开说明,这里具体说明下。我们以ProxiedFilterChain中的filters有两个filter为例说明。A过滤器和B过滤器。(注意ProxiedFilterChain的doFilter是将自己传到doFilter中,所以ProxiedFilterChain中的filter后续调用的 chain.doFilter(request, response)就会再次调用ProxiedFilterChain的doFilter)

第一种情况:如果A.doFilterInternal的preHandle返回true,则执行executeChain。那么就会执行B.doFilterInternal。如果preHandle也返回true,则继续执行executeChain。这时就会触发ProxiedFilterChain#doFilter方法中低第一个if条件,执行原始的filterChain的doFilter方法。

第二种情况:如果A.doFilterInternal的preHandle返回false,会发现不能够再执行B过滤器,也就执行不了原始的filterChain的doFilter方法。(因为不能够调用原始的filterChain的doFilter方法,而servlet.service(request, response)执行必须是最后一个filter处理完后调用filterChain的doFilter。(从org.apache.catalina.core.ApplicationFilterChain的doFilter源码中可以很明显的看出这一点),所以也就不能够触发请求的接口)。在这里就是SpringShiroFilter执行完却没有调用原始的filterChain的doFilter方法。即使SpringShiroFilter是过滤器链上最后一个filter。

图示

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值