Shiro 之 Filter(上):ShiroFilter

在上一篇中,咱們分析了 Shiro Web 應用的入口 —— EnvironmentLoaderListener,它是一個 ServletContextListener,在 Web 容器啓動的時候,它爲咱們建立了兩個很是重要的對象:html

  • WebSecurityManager:它是用於 Web 環境的 SecurityManager 對象,經過讀取 shiro.ini 中 [main] 片斷生成的,咱們能夠經過 SecurityUtils.getSecurityManager 方法獲取該對象。java

  • FilterChainResolver:它是 shiro.ini 中 [urls] 片斷所配置的 Filter Chain 的解析器,可對一個 URL 配置一個或多個 Filter(用逗號分隔),Shiro 也爲咱們提供了幾個默認的 Filter。web

歡迎閱讀《Shiro 源碼分析》第一集: http://my.oschina.net/huangyong/blog/209339

在第二集中,咱們就一塊兒探索一下 Shiro Web 的第二個核心對象 —— ShiroFilter,它是在整個 Shiro Web 應用中請求的門戶,也就是說,全部的請求都會被 ShiroFilter 攔截並進行相應的鏈式處理。apache

咱們仍是使用老套路,從 ShiroFilter 的繼承體系開始吧:api

上圖可見,ShiroFilter 往上居然有五層,最上層是 Filter(即 javax.servlet.Filter),它是 Servlet 規範中的 Filter 接口,代碼以下:安全

public interface Filter {

    void init(FilterConfig filterConfig) throws ServletException;

    void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException;

    void destroy();
}

Filter 接口中的三個方法分別在 Filter 生命週期的三個時期內由 Web 容器來調用,分別是:初始化、執行、銷燬。 session

相信這些內容對於作過 Java Web 開發的朋友而言,都是很是明瞭的,但與 Filter 接口同一級別下居然還有一個名爲 ServletContextSupport 的類,它又是起什麼做用的呢? 架構

打開 ServletContextSupport 的源碼便知,它是 Shiro 爲了封裝 ServletContext 的而提供的一個類,代碼以下:app

/**
 * 封裝 ServletContext
 */
public class ServletContextSupport {

    private ServletContext servletContext;

    public ServletContext getServletContext() {
        return servletContext;
    }

    public void setServletContext(ServletContext servletContext) {
        this.servletContext = servletContext;
    }

    @SuppressWarnings({"UnusedDeclaration"})
    protected String getContextInitParam(String paramName) {
        return getServletContext().getInitParameter(paramName);
    }

    private ServletContext getRequiredServletContext() {
        ServletContext servletContext = getServletContext();
        if (servletContext == null) {
            throw new IllegalStateException();
        }
        return servletContext;
    }

    @SuppressWarnings({"UnusedDeclaration"})
    protected void setContextAttribute(String key, Object value) {
        if (value == null) {
            removeContextAttribute(key);
        } else {
            getRequiredServletContext().setAttribute(key, value);
        }
    }

    @SuppressWarnings({"UnusedDeclaration"})
    protected Object getContextAttribute(String key) {
        return getRequiredServletContext().getAttribute(key);
    }

    protected void removeContextAttribute(String key) {
        getRequiredServletContext().removeAttribute(key);
    }

    @Override
    public String toString() {
        return toStringBuilder().toString();
    }

    protected StringBuilder toStringBuilder() {
        return new StringBuilder(super.toString());
    }
}

經過這個類,咱們能夠方便的操縱 ServletContext 對象(使用其中的屬性),那麼這個 ServletContext 對象又是如何來初始化的呢? 框架

不妨看看 Filter 與 ServletContextSupport 的子類 AbstractFilter 吧,代碼以下:

/**
 * 初始化 ServletContext 並封裝 FilterConfig
 */
public abstract class AbstractFilter extends ServletContextSupport implements Filter {

    protected FilterConfig filterConfig;

    public FilterConfig getFilterConfig() {
        return filterConfig;
    }

    public void setFilterConfig(FilterConfig filterConfig) {
        // 初始化 FilterConfig 與 ServletContext
        this.filterConfig = filterConfig;
        setServletContext(filterConfig.getServletContext());
    }

    protected String getInitParam(String paramName) {
        // 從 FilterConfig 中獲取初始參數
        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) {
            if (e instanceof ServletException) {
                throw (ServletException) e;
            } else {
                throw new ServletException(e);
            }
        }
    }

    protected void onFilterConfigSet() throws Exception {
    }

    public void destroy() {
    }
}

看到這個類的第一感受就是,它對 FilterConfig 進行了封裝,爲何要封裝 FilterConfig 呢?就是想經過它來獲取 ServletContext。可見,在 init 方法中完成了 FilterConfig 的初始化,並提供了一個名爲 onFilterConfigSet 的模板方法,讓它的子類去實現其中的細節。

在閱讀 AbstractFilter 的子類 NameableFilter 的源碼以前,不妨先看看 NameableFilter 實現了一個頗有意思的接口 Nameable,代碼以下:

/**
 * 確保實現該接口的類可進行命名(具備惟一的名稱)
 */
public interface Nameable {

    void setName(String name);
}

僅提供了一個 setName 的方法,目的就是爲了讓其子類可以提供一個惟一的 Filter Name,若是子類不提供怎麼辦呢?

相信 Nameable 的實現類也就是 AbstractFilter 的子類 NameableFilter 會告訴咱們想要的答案,代碼以下:

/**
 * 提供 Filter Name 的 get/set 方法
 */
public abstract class NameableFilter extends AbstractFilter implements Nameable {

    private String name;

    protected String getName() {
        // 若成員變量 name 爲空,則從 FilterConfig 中獲取 Filter Name
        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;
    }

    protected StringBuilder toStringBuilder() {
        String name = getName();
        if (name == null) {
            return super.toStringBuilder();
        } else {
            StringBuilder sb = new StringBuilder();
            sb.append(name);
            return sb;
        }
    }
}

看到了 NameableFilter 中的 getName 方法,咱們應該清楚了,每一個 Filter 必須有一個名字,可經過 setName 方法設置的,若是不設置就取該 Filter 默認的名字,也就是在 web.xml 中配置的 filter-name 了。此外,這裏還經過一個 toStringBuilder 方法完成了相似 toString 方法,不過暫時還沒什麼用途,可能之後會有用。

以上這一切都是爲了讓每一個 Filter 有一個名字,並且這個名字最好是惟一的(這一點在 Shiro 源碼中沒有獲得控制)。此外,在 shiro.ini 的 [urls] 片斷的配置知足必定規則的,例如:

[urls]
/foo = ssl, authc

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

對於 /foo 這個 URL 而言,可前後經過 ssl 與 authc 這兩個 Filter。若是咱們同時配置了兩個 ssl,這個 URL 會被 ssl 攔截兩次嗎?答案是否認的,由於 Shiro 爲咱們提供了一個「一次性 Filter」的原則,也就是保證了每一個請求只能被同一個 Filter 攔截一次,並且僅此一次。

這樣的機制是如何實現的呢?咱們不妨看看 NameableFilter 的子類 OncePerRequestFilter 吧,代碼以下:

/**
 * 確保每一個請求只能被 Filter 過濾一次
 */
public abstract class OncePerRequestFilter extends NameableFilter {

    // 已過濾屬性的後綴名
    public static final String ALREADY_FILTERED_SUFFIX = ".FILTERED";

    // 是否開啓過濾功能
    private boolean enabled = true;

    public boolean isEnabled() {
        return enabled;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    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);
        } else {
            // 若未過濾,則判斷是否未開啓過濾功能(其中 shouldNotFilter 方法將被廢棄,由 isEnabled 方法取代)
            if (!isEnabled(request, response) || shouldNotFilter(request)) {
                // 若未開啓,則進入 FilterChain 中下一個 Filter
                filterChain.doFilter(request, response);
            } else {
                // 若已開啓,則將已過濾屬性設置爲 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;
    }

    @SuppressWarnings({"UnusedParameters"})
    protected boolean isEnabled(ServletRequest request, ServletResponse response) throws ServletException, IOException {
        return isEnabled();
    }

    @Deprecated
    @SuppressWarnings({"UnusedDeclaration"})
    protected boolean shouldNotFilter(ServletRequest request) throws ServletException {
        return false;
    }

    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,在其子類中咱們須要實現該方法的具體細節,那麼誰來實現呢?不妨繼續看下面的 AbstractShiroFilter 吧,代碼以下:

/**
 * 確保可經過 SecurityUtils 獲取 SecurityManager,並執行過濾器操做
 */
public abstract class AbstractShiroFilter extends OncePerRequestFilter {

    // 是否能夠經過 SecurityUtils 獲取 SecurityManager
    private static final String STATIC_INIT_PARAM_NAME = "staticSecurityManagerEnabled";

    private WebSecurityManager securityManager;
    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;
    }

    public boolean isStaticSecurityManagerEnabled() {
        return staticSecurityManagerEnabled;
    }

    public void setStaticSecurityManagerEnabled(boolean staticSecurityManagerEnabled) {
        this.staticSecurityManagerEnabled = staticSecurityManagerEnabled;
    }

    // 這是 AbstractFilter 提供的在 init 時須要執行的方法
    protected final void onFilterConfigSet() throws Exception {
        // 從 web.xml 中讀取 staticSecurityManagerEnabled 參數(默認爲 false)
        applyStaticSecurityManagerEnabledConfig();
        // 初始化(在子類中實現)
        init();
        // 確保 SecurityManager 必須存在
        ensureSecurityManager();
        // 若已開啓 static 標誌,則將當前的 SecurityManager 放入 SecurityUtils 中,之後能夠隨時獲取
        if (isStaticSecurityManagerEnabled()) {
            SecurityUtils.setSecurityManager(getSecurityManager());
        }
    }

    private void applyStaticSecurityManagerEnabledConfig() {
        String value = getInitParam(STATIC_INIT_PARAM_NAME);
        if (value != null) {
            Boolean b = Boolean.valueOf(value);
            if (b != null) {
                setStaticSecurityManagerEnabled(b);
            }
        }
    }

    public void init() throws Exception {
    }

    private void ensureSecurityManager() {
        // 首先獲取當前的 SecurityManager,若不存在,則建立默認的 SecurityManager(即 DefaultWebSecurityManager)
        WebSecurityManager securityManager = getSecurityManager();
        if (securityManager == null) {
            securityManager = createDefaultSecurityManager();
            setSecurityManager(securityManager);
        }
    }

    protected WebSecurityManager createDefaultSecurityManager() {
        return new DefaultWebSecurityManager();
    }

    // 這是 OncePerRequestFilter 提供的在 doFilter 時須要執行的方法
    protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain) throws ServletException, IOException {
        Throwable t = null;
        try {
            // 返回被 Shiro 包裝過的 Request 與 Response 對象
            final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);
            final ServletResponse response = prepareServletResponse(request, servletResponse, chain);
            // 建立 Shiro 的 Subject 對象
            final Subject subject = createSubject(request, response);
            // 使用異步的方式執行相關操做
            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) {
            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;
            }
            throw new ServletException(t);
        }
    }

    @SuppressWarnings({"UnusedDeclaration"})
    protected ServletRequest prepareServletRequest(ServletRequest request, ServletResponse response, FilterChain chain) {
        ServletRequest toUse = request;
        if (request instanceof HttpServletRequest) {
            // 獲取包裝後的 Request 對象(使用 ShiroHttpServletRequest 進行包裝)
            HttpServletRequest http = (HttpServletRequest) request;
            toUse = wrapServletRequest(http);
        }
        return toUse;
    }

    protected ServletRequest wrapServletRequest(HttpServletRequest orig) {
        return new ShiroHttpServletRequest(orig, getServletContext(), isHttpSessions());
    }

    protected boolean isHttpSessions() {
        return getSecurityManager().isHttpSessionMode();
    }

    @SuppressWarnings({"UnusedDeclaration"})
    protected ServletResponse prepareServletResponse(ServletRequest request, ServletResponse response, FilterChain chain) {
        ServletResponse toUse = response;
        if (!isHttpSessions() && (request instanceof ShiroHttpServletRequest) && (response instanceof HttpServletResponse)) {
            // 獲取包裝後的 Response 對象(使用 ShiroHttpServletResponse 進行包裝)
            toUse = wrapServletResponse((HttpServletResponse) response, (ShiroHttpServletRequest) request);
        }
        return toUse;
    }

    protected ServletResponse wrapServletResponse(HttpServletResponse orig, ShiroHttpServletRequest request) {
        return new ShiroHttpServletResponse(orig, getServletContext(), request);
    }

    protected WebSubject createSubject(ServletRequest request, ServletResponse response) {
        return new WebSubject.Builder(getSecurityManager(), request, response).buildWebSubject();
    }

    @SuppressWarnings({"UnusedDeclaration"})
    protected void updateSessionLastAccessTime(ServletRequest request, ServletResponse response) {
        // 僅對本地 Session 作以下操做
        if (!isHttpSessions()) {
            // 獲取 Subject(其實是從 ThreadLocal 中獲取的)
            Subject subject = SecurityUtils.getSubject();
            if (subject != null) {
                // 從 Subject 中獲取 Session
                Session session = subject.getSession(false);
                if (session != null) {
                    // 更新 Session 對象的 lastAccessTime 屬性
                    session.touch();
                }
            }
        }
    }

    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;
        // 獲取 FilterChainResolver,若不存在,則返回原始的 FilterChain
        FilterChainResolver resolver = getFilterChainResolver();
        if (resolver == null) {
            return origChain;
        }
        // 經過 FilterChainResolver 獲取 ProxiedFilterChain
        FilterChain resolved = resolver.getChain(request, response, origChain);
        if (resolved != null) {
            chain = resolved;
        }
        return chain;
    }
}

這個 AbstractShiroFilter 類代碼稍微有點長,由於它幹了許多的事情,主要實現了兩個模板方法:onFilterConfigSet 與 doFilterInternal,以上代碼中均已對它們作了詳細的註釋。

其中,在 onFilterConfigSet 中實際上提供了一個框架,只是將 SecurityManager 放入 SecurityUtils 這個工具類中,至於具體行爲仍是放在子類的 init 方法中去實現,而這個子類就是 ShiroFilter,代碼以下:

/**
 * 初始化過濾器
 */
public class ShiroFilter extends AbstractShiroFilter {

    @Override
    public void init() throws Exception {
        // 從 ServletContext 中獲取 WebEnvironment(該對象已經過 EnvironmentLoader 建立)
        WebEnvironment env = WebUtils.getRequiredWebEnvironment(getServletContext());

        // 將 WebEnvironment 中的 WebSecurityManager 放入 AbstractShiroFilter 中
        setSecurityManager(env.getWebSecurityManager());

        // 將 WebEnvironment 中的 FilterChainResolver 放入 AbstractShiroFilter 中
        FilterChainResolver resolver = env.getFilterChainResolver();
        if (resolver != null) {
            setFilterChainResolver(resolver);
        }
    }
}

在 ShiroFilter 中只用作初始化的行爲,就是從 WebEnvironment 中分別獲取 WebSecurityManager 與 FilterChainResolver,其它的事情都由它的父類去實現了。

到此爲止,ShiroFilter 的源碼已基本分析完畢,固然還有些很是有意思的代碼,這裏沒有進行分析,例如:

  • 經過 ShiroHttpServletRequest 來包裝 Request

  • 經過 ShiroHttpServletResponse 來包裝 Response

  • 經過 Session 來代理 HttpSession

  • 提供 FilterChain 的代理機制

  • 使用 ThreadContext 來保證線程安全

這些有意思的代碼,我就不繼續分析了,留點滋味讓你們去慢慢品嚐吧!

最後須要補充說明的是,Shiro 的 Filter 架構體系是很是龐大的,這裏僅對 ShiroFilter 進行了分析,整個 Filter 靜態結構看起來是這樣的:


可見,在 OncePerRequestFilter 下有兩個分支,本文只分析了 ShiroFilter 這個分支,另外還有一個 AdviceFilter 分支,它提供了 AOP 功能的 Filter,這些 Filter 就是 Shiro 爲咱們提供的默認 Filter:

名稱 類名
anon org.apache.shiro.web.filter.authc.AnonymousFilter
authc org.apache.shiro.web.filter.authc.FormAuthenticationFilter
authcBasic org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
logout org.apache.shiro.web.filter.authc.LogoutFilter
noSessionCreation org.apache.shiro.web.filter.session.NoSessionCreationFilter
perms org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
port org.apache.shiro.web.filter.authz.PortFilter
rest org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter
roles org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
ssl org.apache.shiro.web.filter.authz.SslFilter
user org.apache.shiro.web.filter.authc.UserFilter

以上這些 Filter 是如何實現的呢?有機會再與你們分享《Shrio 源碼分析》,感謝您閱讀本文!

相關文章
相關標籤/搜索