shiro的代理過濾器

在配置shiro的時候第一件事情就是在web.xml文件中配置一個由spring提供的類:org.springframework.web.filter.DelegatingFilterProxy 按照字面的翻譯這應該是一個代理過濾器的策略。css

<filter>
        <filter-name>shiroFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <async-supported>true</async-supported>
        <init-param>
            <param-name>targetFilterLifecycle</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>

    <filter-mapping>
        <filter-name>shiroFilter</filter-name>
        <url-pattern>/*</url-pattern>
        <dispatcher>REQUEST</dispatcher>
    </filter-mapping>

這個類實際上是將上下文中名稱爲shiroFilter的類作成一個代理過濾器。該類將從spring上下文中找到本身要代理的過濾器類,並負責初始化和銷燬該過濾器。並在每次發起的攔截請求是先走該類的方法invokeDelegate()。java

##初始化web

protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
		Filter delegate = wac.getBean(getTargetBeanName(), Filter.class);
		if (isTargetFilterLifecycle()) {
			delegate.init(getFilterConfig());
		}
		return delegate;
	}

初始化過程就是將配置文件中的參數注入到ShiroFilterFactoryBean中的SpringShiroFilter中。spring

##請求調用 當咱們在瀏覽器中打印了一個可以被shiro攔截的uri的時候首先會進入到DelegatingFilterProxy的doFilter方法中。該方法首先判斷被代理的攔截器是否被初始化,若是沒有則實行懶加載策略初始化shiro攔截器。不然則開始調用shiro攔截器去執行攔截。設計模式

public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
		// Lazily initialize the delegate if necessary.
		Filter delegateToUse = this.delegate;
		if (delegateToUse == null) {//懶加載過程
			synchronized (this.delegateMonitor) {
				if (this.delegate == null) {
					WebApplicationContext wac = findWebApplicationContext();
					if (wac == null) {
						throw new IllegalStateException("No WebApplicationContext found: no ContextLoaderListener registered?");
					}
					this.delegate = initDelegate(wac);
				}
				delegateToUse = this.delegate;
			}
		}

	
		invokeDelegate(delegateToUse, request, response, filterChain);
	}
	protected void invokeDelegate(
			Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
	// 調用真正shirofilter
		delegate.doFilter(request, response, filterChain);
	}

delegate實例最終類型是SpringShiroFilter。該類集成在AbstractShiroFilter。瀏覽器

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);
            }
        }
    }

AbstractShiroFilter類繼承自OncePerRequestFiltersession

public abstract class AbstractShiroFilter extends OncePerRequestFilter{}

因此咱們首先來看看OncePerRequestFilter。mvc

public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
        if ( request.getAttribute(alreadyFilteredAttributeName) != null ) {//不攔截已經被攔截處理的請求
            log.trace("Filter '{}' already executed.  Proceeding without invoking this filter.", getName());
            filterChain.doFilter(request, response);
        } else //不過濾被配置不過濾的請求
            if (/* added in 1.2: */ !isEnabled(request, response) ||
                /* retain backwards compatibility: */ shouldNotFilter(request) ) {
            log.debug("Filter '{}' is not enabled for the current request.  Proceeding without invoking this filter.",
                    getName());
            filterChain.doFilter(request, response);
        } else {
            //未來自該過濾請求(如shiroFitler)設置成已經被過濾
            log.trace("Filter '{}' not yet executed.  Executing now.", getName());
            request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);

            try {
            //執行shiro的過濾邏輯。
                doFilterInternal(request, response, filterChain);
            } finally {
                // Once the request has finished, we're done and we don't
                // need to mark as 'already filtered' any more.
                request.removeAttribute(alreadyFilteredAttributeName);
            }
        }
    }

若是到達的請求沒有被OncePerRequestFilter過濾掉,則會走shiro的攔截請求。值得一塊兒的是,FilterChain chain實際上是spring的servlet上下文。這個是爲了包裝request、response的時候將servlet上下文也放置進來。app

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

        Throwable t = null;

        try {
            final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);//包裝request
            final ServletResponse response = prepareServletResponse(request, servletResponse, chain);//包裝response

            final Subject subject = createSubject(request, response);//建立subject對象

            //執行回調函數。
            //一、更新session的最後訪問時間
            //二、執行攔截器鏈。
            subject.execute(new Callable() {
                public Object call() throws Exception {
                    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);
        }
    }

shiro的攔截器首先將request和response進行了包裝。並建立subject執行回調函數。該函數主要執行了兩個部分:一、更新session的最後訪問時間 二、執行攔截器鏈。重點看下執行攔截器鏈邏輯。框架

protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain)
            throws IOException, ServletException {
        FilterChain chain = getExecutionChain(request, response, origChain);//得到攔截器鏈
        chain.doFilter(request, response);//執行攔截器鏈
    }

攔截器邏輯主要分紅了兩個部分:一、得到攔截器鏈 二、執行攔截器鏈。 一、得到攔截器鏈

protected FilterChain getExecutionChain(ServletRequest request, ServletResponse response, FilterChain origChain) {
        FilterChain chain = origChain;
//1.1得到攔截器鏈解析器
        FilterChainResolver resolver = getFilterChainResolver();
        if (resolver == null) {
            log.debug("No FilterChainResolver configured.  Returning original FilterChain.");
            return origChain;
        }
//1.2得到最終的攔截器執行鏈。
        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;
    }

1.一、得到攔截器鏈解析器最終得到了PathMatchingFilterChainResolver的實例。 1.二、得到最終的攔截器執行鏈是執行PathMatchingFilterChainResolver中的getChain方法。

public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) {
//得到過濾鏈管理器。該管理器是在初始化SpringShiroFilter時候注入進來的。
        FilterChainManager filterChainManager = getFilterChainManager();
        if (!filterChainManager.hasChains()) {
            return null;
        }
//得到請求uri
        String requestURI = getPathWithinApplication(request);
//uri和攔截器鏈作匹配。
        for (String pathPattern : filterChainManager.getChainNames()) {
//若是匹配成功
            if (pathMatches(pathPattern, requestURI)) {
            //1.3返回一個代理過濾器
                return filterChainManager.proxy(originalChain, pathPattern);
            }
        }

        return null;
    }
    ```
1.3是將當前的servlet上下文和請求uri一塊兒包裝成爲一個過濾器代理。
```java
FilterChainManager類
 public FilterChain proxy(FilterChain original, String chainName) {
        NamedFilterList configured = getChain(chainName);
        if (configured == null) {
            String msg = "There is no configured chain under the name/key [" + chainName + "].";
            throw new IllegalArgumentException(msg);
        }
        return configured.proxy(original);
    }
   public class SimpleNamedFilterList implements NamedFilterList {
     public FilterChain proxy(FilterChain orig) {
        return new ProxiedFilterChain(orig, this);
    }
   }

二、執行攔截器鏈 剛纔看到得到攔截器的過程其實就是生成了一個類型爲ProxiedFilterChain的實例。那麼執行天然就是該類的doFilter方法。

public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
 //若是過濾器鏈爲空,則執行servlet上下文的過濾器。
        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 + "]");
            }
            this.filters.get(this.index++).doFilter(request, response, this);
        }
    }

這裏面涵蓋了一個過濾器的設計模式。當咱們給某個uri設計多個攔截器的時候,這幾個攔截器是怎麼連續執行的呢?換句話說上面的index是如何一個一個累加直到this.filters.size() == this.index條件成立跳出循環的呢?咱們來看下shiro裏面的攔截器鏈實現。 首先觀察一下shiro配置文章的這個段

/static/** = anon
                /js/**  =anon
                /css/** =anon
                /favicon.ico =anon
                /images/** = anon
                /logout = logout
                /user/login=authc
                /** =sysUser,onlineSession,syncOnlineSession,perms,roles

咱們不難發現這裏面的攔截器都擴展自接口AdviceFilter或接口AdviceFilter的實現。給接口做用就是在執行攔截器先後添加一些邏輯。

public abstract class AdviceFilter extends OncePerRequestFilter {
   protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        return true;
    }
    protected void postHandle(ServletRequest request, ServletResponse response) throws Exception {
    }
    public void afterCompletion(ServletRequest request, ServletResponse response, Exception exception) throws Exception {
    }
    protected void executeChain(ServletRequest request, ServletResponse response, FilterChain chain) throws Exception {
        chain.doFilter(request, response);
    }
    //執行邏輯
    public void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
            throws ServletException, IOException {
        Exception exception = null;
        try {
    //執行前的邏輯,若是未返回true則不執行過濾器邏輯。
            boolean continueChain = preHandle(request, response);
            if (log.isTraceEnabled()) {
                log.trace("Invoked preHandle method.  Continuing chain?: [" + continueChain + "]");
            }
//執行過濾器邏輯
            if (continueChain) {
                executeChain(request, response, chain);
            }
//執行後邏輯。
            postHandle(request, response);
            if (log.isTraceEnabled()) {
                log.trace("Successfully invoked postHandle method");
            }
        } catch (Exception e) {
            exception = e;
        } finally {
            cleanup(request, response, exception);
        }
    }
    protected void cleanup(ServletRequest request, ServletResponse response, Exception existing)
            throws ServletException, IOException {
    }
}

AdviceFilter擴展自OncePerRequestFilter類。

public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
        if ( request.getAttribute(alreadyFilteredAttributeName) != null ) {
            log.trace("Filter '{}' already executed.  Proceeding without invoking this filter.", getName());
            //執行過濾器鏈下一個過濾器
            filterChain.doFilter(request, response);
        } else //noinspection deprecation
            if (/* added in 1.2: */ !isEnabled(request, response) ||
                /* retain backwards compatibility: */ shouldNotFilter(request) ) {
            log.debug("Filter '{}' is not enabled for the current request.  Proceeding without invoking this filter.",
                    getName());
                       //執行過濾器鏈下一個過濾器
            filterChain.doFilter(request, response);
        } else {
            // Do invoke this filter...
            log.trace("Filter '{}' not yet executed.  Executing now.", getName());
            request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);

            try {
            //執行本過濾器。
                doFilterInternal(request, response, filterChain);
            } finally {
                // Once the request has finished, we're done and we don't
                // need to mark as 'already filtered' any more.
                request.removeAttribute(alreadyFilteredAttributeName);
            }
        }
    }

這兩個類的共同特色就是執行doFilterInternal方法是執行本過濾器的過濾邏輯。執行filterChain是執行過利器鏈裏面下一個過濾器的執行邏輯。filterChain的類型都是ProxiedFilterChain。

public class ProxiedFilterChain implements FilterChain {

    //TODO - complete JavaDoc

    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.");
        }
        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) {
            //we've reached the end of the wrapped chain, so invoke the original one:
            if (log.isTraceEnabled()) {
                log.trace("Invoking original filter chain.");
            }
            //若是過濾器爲空,或者過濾器鏈已經執行完,則orig裏面若是有過濾器邏輯則執行。
            this.orig.doFilter(request, response);
        } else {
            if (log.isTraceEnabled()) {
                log.trace("Invoking wrapped filter at index [" + this.index + "]");
            }
            //得到第index個過濾器並執行。
            this.filters.get(this.index++).doFilter(request, response, this);
        }
    }

過濾器模式簡單地說就是:到了某個過濾器他會執行本身的邏輯(doFilterInternal方法),執行完本身的邏輯以後,他還會到剛剛傳過來的過濾器鏈的doFilter方法返回到過濾器鏈下一個過濾器繼續執行。 這裏也有個疑惑,就是過濾器鏈執行完畢以後應該怎麼返回到springmvc的DispatcherServlet裏面的service()方法繼續spring的邏輯呢? 答案就在這行: this.orig.doFilter(request, response); 咱們在配置到web.xml文件中的過濾器和servlet的執行順序首先按照配置的前後順序執行過濾器,當過濾器執行完畢的時候會調用 FilterChain的doFilter()方法返回到servlet環境,而後再原路返回。那麼爲何orig.doFilter可以返回到servlet呢,查看源碼filterChian既不是servlet的代理也不是servlet的包裝器。那什麼時機讓orig成爲了servlet返回路徑的呢? dubug到這裏咱們發現瞭如圖的類:ServletHandler。原來這個ServletHandler是jetty用於管理Filter、FilterMapping、Servlet、ServletMapping的容器。(我用的是jetty,本段環境爲jetty環境)。以及用於實現一次請求所對應的Filter鏈和Servlet執行流程的類。對Servlet的框架實現中,它也被認爲是Handler鏈的末端,於是在它的doHandle()方法中沒有調用nextHandle()方法。至於具體實現就等分析jetty源碼的時候闡述了。

相關文章
相關標籤/搜索