Apache Shiro學習筆記(六)Shiro Filter介紹

魯春利的工作筆記,好記性不如爛筆頭



ShiroFilter

wKiom1egB4Gi7fg1AAHx7Px55zM737.jpg


ShiroFilter,它是在整個 Shiro Web 應用中請求的門戶,所有的請求都會被 ShiroFilter 攔截並進行相應的鏈式處理。ShiroFilter 往上有五層,最上層是 Filter(即 javax.servlet.Filter),它是 Servlet 規範中的 Filter 接口。


1、AbstractFilter

    Shiro通過抽象類對Servlet的Filter接口進行了封裝,並通過繼承ServletContextSupport對ServletContext也進行了封裝。

package org.apache.shiro.web.servlet;
// ServletContextSupport對ServletContext進行了封裝
public abstract class AbstractFilter extends ServletContextSupport implements Filter {

    protected FilterConfig filterConfig;

    public FilterConfig getFilterConfig() {
        return filterConfig;
    }
    
    // 初始化 FilterConfig 與 ServletContext
    public void setFilterConfig(FilterConfig filterConfig) {
        this.filterConfig = filterConfig;
        // 調用ServletContextSupport的方法來封裝ServletContext
        setServletContext(filterConfig.getServletContext());
    }
    
    // 從 FilterConfig 中獲取初始參數
    protected String getInitParam(String paramName) {
        FilterConfig config = getFilterConfig();
        if (config != null) {
            return StringUtils.clean(config.getInitParameter(paramName));
        }
        return null;
    }

    public final void init(FilterConfig filterConfig) throws ServletException {
        // 初始化 FilterConfig
        setFilterConfig(filterConfig);
        try {
            // 在子類中實現該模板方法
            onFilterConfigSet();
        } catch (Exception e) {
            // 異常信息
        }
    }
}

Shiro對ServletContext的封裝

Shiro 爲了封裝 ServletContext 的而提供的一個類ServletContextSupport

package org.apache.shiro.web.servlet;
// 
public class ServletContextSupport {
    private ServletContext servletContext = null;

    public ServletContext getServletContext() {
        return servletContext;
    }

    public void setServletContext(ServletContext servletContext) {
        this.servletContext = servletContext;
    }
    
    // 其他代碼略
}


2、NameableFilter

提供了Filter Name的獲取方式。

package org.apache.shiro.web.servlet;

public abstract class NameableFilter extends AbstractFilter implements Nameable {
    /**
     * The name of this filter, unique within an application.
     */
    private String name;
    
    // 若成員變量 name 爲空,則從 FilterConfig 中獲取 Filter Name
    protected String getName() {
        if (this.name == null) {
            FilterConfig config = getFilterConfig();
            if (config != null) {
                this.name = config.getFilterName();
            }
        }
        return this.name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
}

    每個 Filter 必須有一個名字,可通過 setName 方法設置的,如果不設置就取該 Filter 默認的名字,也就是在 web.xml 中配置的 filter-name 了。


3、OncePerRequestFilter

    NameableFilter是爲了讓每個 Filter 有一個名字,而且這個名字必須是唯一的。此外,在 shiro.ini 的 [urls] 片段的配置要求滿足一定規則,例如:

[urls]
/foo = ssl, authc

    等號左邊的是 URL,右邊的是 Filter Chian,一個或多個 Filter,每個 Filter 用逗號進行分隔。

    對於 /foo 這個 URL 而言,可先後通過 ssl 與 authc 這兩個 Filter。如果我們同時配置了兩個 ssl,這個 URL 會被 ssl 攔截兩次嗎?答案是否定的,因爲 Shiro 爲我們提供了一個“一次性Filter”的原則,也就是保證了每個請求只能被同一個 Filter 攔截一次,而且僅此一次。

package org.apache.shiro.web.servlet;

public abstract class OncePerRequestFilter extends NameableFilter {

     /**
     * 已執行過的過濾器("already filtered")附加的後綴名
     *
     * @see #getAlreadyFilteredAttributeName
     */
    public static final String ALREADY_FILTERED_SUFFIX = ".FILTERED";

    /**
     * 是否開啓過濾功能
     *
     * @see #isEnabled()
     */
    private boolean enabled = true; //most filters wish to execute when configured, so default to true

    public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        // 獲取 Filter 已過濾的屬性名
        String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
        // 判斷是否已過濾
        if ( request.getAttribute(alreadyFilteredAttributeName) != null ) {
            // 若已過濾,則進入 FilterChain 中下一個 Filter
            filterChain.doFilter(request, response);
            
        // 若未過濾,則判斷是否未開啓過濾功能(其中 shouldNotFilter 方法將被廢棄
        } else if (!isEnabled(request, response) || shouldNotFilter(request) ) {
            // 若未開啓,則進入 FilterChain 中下一個 Filter
            filterChain.doFilter(request, response);
        } else {
            // Do invoke this filter...(執行過濾)
            // 將已過濾屬性設置爲 true(只要保證 Request 中有這個屬性即可)
            request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);

            try {
                // 在子類中執行具體的過濾操作
                doFilterInternal(request, response, filterChain);
            } finally {
                // 當前 Filter 執行結束需移除 Request 中的已過濾屬性
                request.removeAttribute(alreadyFilteredAttributeName);
            }
        }
    }
    
    protected String getAlreadyFilteredAttributeName() {
        String name = getName();
        if (name == null) {
            name = getClass().getName();
        }
        return name + ALREADY_FILTERED_SUFFIX;
    }
    // 抽象方法,由子類實現
    protected abstract void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
            throws ServletException, IOException;
}

    如何確保每個請求只會被同一個 Filter 攔截一次呢?Shiro 提供了一個超簡單的解決方案:在 Requet 中放置一個後綴爲 .FILTERED 的屬性,在執行具體攔截操作(即 doFilterInternal 方法)之前放入該屬性,執行完畢後移除該屬性。

    在 Shiro 的 Filter Chian 配置中,如果我們想禁用某個 Filter,如何實現呢?OncePerRequestFilter 也爲我們提供了一個 enabled 的屬性,方便我們可以在 shiro.ini 中隨時禁用某個 Filter,例如:

[main]
ssl.enabled = false

[urls]
/foo = ssl, authc

    這樣一來 ssl 這個 Filter 就被我們給禁用了,以後想開啓 ssl 的話,完全不需要在 urls配置中一個個手工來添加,只需把 ssl.enabled 設置爲 true,或註釋掉該行,或直接刪除該行即可。

    可見,OncePerRequestFilter 給我們提供了一個模板方法doFilterInternal,在其子類中我們需要實現該方法的具體細節,那麼誰來實現呢?


3.1、AbstractShiroFilter

package org.apache.shiro.web.servlet;
/**
 * 確保可通過 SecurityUtils 獲取 SecurityManager,並執行過濾器操作
 */
public abstract class AbstractShiroFilter extends OncePerRequestFilter {
    // 是否可以通過 SecurityUtils 獲取 SecurityManager
    private static final String STATIC_INIT_PARAM_NAME = "staticSecurityManagerEnabled";
    
    // Reference to the security manager used by this filter
    private WebSecurityManager securityManager;

    // Used to determine which chain should handle an incoming request/response
    private FilterChainResolver filterChainResolver;
    
    private boolean staticSecurityManagerEnabled;

    protected AbstractShiroFilter() {
        this.staticSecurityManagerEnabled = false;
    }

    public WebSecurityManager getSecurityManager() {
        return securityManager;
    }

    public void setSecurityManager(WebSecurityManager sm) {
        this.securityManager = sm;
    }

    public FilterChainResolver getFilterChainResolver() {
        return filterChainResolver;
    }

    public void setFilterChainResolver(FilterChainResolver filterChainResolver) {
        this.filterChainResolver = filterChainResolver;
    }
    
    // 在AbstractFilter.ini()中調用子類的實現
    protected final void onFilterConfigSet() throws Exception {
        //added in 1.2 for SHIRO-287:
        // 從 web.xml 中讀取 staticSecurityManagerEnabled 參數(默認爲 false)
        applyStaticSecurityManagerEnabledConfig();
        // 初始化(在子類中實現)
        init();
        // 確保 SecurityManager 必須存在
        ensureSecurityManager();
        //added in 1.2 for SHIRO-287:
        // 若已開啓 static 標誌,則將當前的 SecurityManager 放入 SecurityUtils 中,以後可以隨時獲取
        if (isStaticSecurityManagerEnabled()) {
            SecurityUtils.setSecurityManager(getSecurityManager());
        }
    }
    
    public void init() throws Exception {
        // 空方法體
    }
    
    // OncePerRequestFilter.doFilter 時需要執行的方法
    protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, 
                                    final     FilterChain chain)throws ServletException, IOException {

        Throwable t = null;

        try {
            // 通過ShiroHttpServletRequest對ServletRequest進行了封裝
            final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);
            // 通過ShiroHttpServletResponse對ServletResponse進行了封裝
            final ServletResponse response = prepareServletResponse(request, servletResponse, chain);
            // 創建 Shiro 的 Subject 對象(WebSubject)
            final Subject subject = createSubject(request, response);

            // 使用異步的方式執行相關操作
            //noinspection unchecked
            subject.execute(new Callable() {
                public Object call() throws Exception {
                    // 更新 Session 的最後訪問時間
                    updateSessionLastAccessTime(request, response);
                    // 執行 Shiro 的 Filter Chain
                    executeChain(request, response, chain);
                    return null;
                }
            });
        } catch (ExecutionException ex) {
            // 異常處理
        } catch (Throwable throwable) {
            // 異常處理
        }

        if (t != null) {
            // 異常處理
        }
    }
    
    protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain)
            throws IOException, ServletException {
        // 獲取 Shiro 代理後的 FilterChain 對象,並進行鏈式處理
        FilterChain chain = getExecutionChain(request, response, origChain);
        chain.doFilter(request, response);
    }
    
    protected FilterChain getExecutionChain(ServletRequest request, ServletResponse response, FilterChain origChain) {
        FilterChain chain = origChain;

        // 在ShiroFilter的init方法中設置FilterChainResolver
        FilterChainResolver resolver = getFilterChainResolver();
        if (resolver == null) {
            log.debug("No FilterChainResolver configured.  Returning original FilterChain.");
            return origChain;
        }
        // 通過 FilterChainResolver(PathMatchingFilterChainResolver) 獲取 ProxiedFilterChain
        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;
    }
}


3.1.1、ShiroFilter

package org.apache.shiro.web.servlet;

public class ShiroFilter extends AbstractShiroFilter {
    @Override
    public void init() throws Exception {
        // 從 ServletContext 中獲取 WebEnvironment(該對象已通過 EnvironmentLoader 創建)
        // 實際實現爲:通過ServletContext獲取到web.xml文件中定義的shiroEnvironmentClass
        WebEnvironment env = WebUtils.getRequiredWebEnvironment(getServletContext());

        // IniWebEnvironment.init會生成SecurityManager,在getWebSecurityManager時直接獲取到
        setSecurityManager(env.getWebSecurityManager());
        
        // 通過IniFilterChainResolverFactory.createDefaultInstance獲取PathMatchingFilterChainResolver
        FilterChainResolver resolver = env.getFilterChainResolver();
        if (resolver != null) {
            setFilterChainResolver(resolver);
        }
    }
}

在 ShiroFilter 中只用做初始化的行爲,就是從 WebEnvironment 中分別獲取 WebSecurityManager與FilterChainResolver,其它的事情都由它的父類去實現了。
實際上ShiroFilter 還實現了一些其他的封裝,例如:
    通過 ShiroHttpServletRequest 來包裝 Request
    通過 ShiroHttpServletResponse 來包裝 Response
    通過 Session 來代理 HttpSession
    提供 FilterChain 的代理機制
    使用 ThreadContext 來保證線程安全


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章