Shiro權限管理框架(三):Shiro中權限過濾器的初始化流程和實現原理

本篇是Shiro系列第三篇,Shiro中的過濾器初始化流程和實現原理。Shiro基於URL的權限控制是經過Filter實現的,本篇從咱們注入的ShiroFilterFactoryBean開始入手,翻看源碼追尋Shiro中的過濾器的實現原理。html


初始化流程

ShiroFilterFactoryBean實現了FactoryBean接口,那麼Spring在初始化的時候必然會調用ShiroFilterFactoryBean的getObject()獲取實例,而ShiroFilterFactoryBean也在此時作了一系列初始化操做。java

關於FactoryBean的介紹和實現方式另外也記了一篇:https://www.guitu18.com/post/2019/04/28/33.htmlweb

在getObject()中會調用createInstance(),初始化相關的東西都在這裏了,代碼貼過來去掉了註釋和校驗相關的代碼。數據庫

protected AbstractShiroFilter createInstance() throws Exception {
        SecurityManager securityManager = getSecurityManager();
        FilterChainManager manager = createFilterChainManager();
        PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();
        chainResolver.setFilterChainManager(manager);
        return new SpringShiroFilter((WebSecurityManager) securityManager, chainResolver);
    }

這裏面首先獲取了咱們在ShiroConfig中注入好參數的SecurityManager,再次強調,這位是Shiro中的核心組件。而後建立了一個FilterChainManager,這個類看名字就知道是用來管理和操做過濾器執行鏈的,咱們來看它的建立方法createFilterChainManager()。服務器

protected FilterChainManager createFilterChainManager() {
        DefaultFilterChainManager manager = new DefaultFilterChainManager();
        Map<String, Filter> defaultFilters = manager.getFilters();
        for (Filter filter : defaultFilters.values()) {
            applyGlobalPropertiesIfNecessary(filter);
        }
        Map<String, Filter> filters = getFilters();
        if (!CollectionUtils.isEmpty(filters)) {
            for (Map.Entry<String, Filter> entry : filters.entrySet()) {
                String name = entry.getKey();
                Filter filter = entry.getValue();
                applyGlobalPropertiesIfNecessary(filter);
                if (filter instanceof Nameable) {
                    ((Nameable) filter).setName(name);
                }
                manager.addFilter(name, filter, false);
            }
        }
        Map<String, String> chains = getFilterChainDefinitionMap();
        if (!CollectionUtils.isEmpty(chains)) {
            for (Map.Entry<String, String> entry : chains.entrySet()) {
                String url = entry.getKey();
                String chainDefinition = entry.getValue();
                manager.createChain(url, chainDefinition);
            }
        }
        return manager;
    }

第一步new了一個DefaultFilterChainManager,在它的構造方法中將filters和filterChains兩個成員變量都初始化爲一個能保持插入順序的LinkedHashMap了,以後再調用addDefaultFilters()添加Shiro內置的一些過濾器。app

public DefaultFilterChainManager() {
        this.filters = new LinkedHashMap<String, Filter>();
        this.filterChains = new LinkedHashMap<String, NamedFilterList>();
        addDefaultFilters(false);
    }
protected void addDefaultFilters(boolean init) {
        for (DefaultFilter defaultFilter : DefaultFilter.values()) {
            addFilter(defaultFilter.name(), defaultFilter.newInstance(), init, false);
        }
    }

這裏用枚舉列出了全部Shiro內置過濾器的實例。框架

public enum DefaultFilter {
    anon(AnonymousFilter.class),
    authc(FormAuthenticationFilter.class),
    authcBasic(BasicHttpAuthenticationFilter.class),
    logout(LogoutFilter.class),
    noSessionCreation(NoSessionCreationFilter.class),
    perms(PermissionsAuthorizationFilter.class),
    port(PortFilter.class),
    rest(HttpMethodPermissionFilter.class),
    roles(RolesAuthorizationFilter.class),
    ssl(SslFilter.class),
    user(UserFilter.class);
}

以上代碼有省略,上面列出的枚舉類型正好對應Shiro第一篇提到的Shiro內置的一些過濾器,這些過濾器正好是在這裏初始化並添加到過濾器執行鏈中的,每一個過濾器都有不一樣的功能,咱們經常使用的其實只有前面兩個。jsp

img

回到上上一步中,DefaultFilterChainManager初始化完成後,遍歷了每個默認的過濾器並調用了applyGlobalPropertiesIfNecessary()設置一些必要的全局屬性。post

private void applyGlobalPropertiesIfNecessary(Filter filter) {
        applyLoginUrlIfNecessary(filter);
        applySuccessUrlIfNecessary(filter);
        applyUnauthorizedUrlIfNecessary(filter);
    }

在這個方法中調用了三個方法,三個方法邏輯是同樣的,分別是設置loginUrl、successUrl和unauthorizedUrl,咱們就看第一個applyLoginUrlIfNecessary()。學習

private void applyLoginUrlIfNecessary(Filter filter) {
        String loginUrl = getLoginUrl();
        if (StringUtils.hasText(loginUrl) && (filter instanceof AccessControlFilter)) {
            AccessControlFilter acFilter = (AccessControlFilter) filter;
            String existingLoginUrl = acFilter.getLoginUrl();
            if (AccessControlFilter.DEFAULT_LOGIN_URL.equals(existingLoginUrl)) {
                acFilter.setLoginUrl(loginUrl);
            }
        }
    }

看方法名就知道是要設置loginUrl,若是咱們配置了loginUrl,那麼會將AccessControlFilter中默認的loginUrl替換爲咱們設置的值,默認的loginUrl爲/login.jsp。後面兩個方法道理同樣,都是將咱們設置的參數替換進去,只不過第三個認證失敗跳轉URL的默認值爲null。

繼續回到上一步,Map<String, Filter> filters = getFilters(); 這裏是獲取咱們自定義的過濾器,默認是爲空的,若是咱們配置了自定義的過濾器,那麼會將其添加到filters中。至此filters中包含着Shiro內置的過濾器和咱們配置的全部過濾器。

下一步,遍歷filterChainDefinitionMap,這個filterChainDefinitionMap就是咱們在ShiroConfig中注入進去的攔截規則配置。這裏是根據咱們配置的過濾器規則建立建立過濾器執行鏈。

public void createChain(String chainName, String chainDefinition) {
        String[] filterTokens = splitChainDefinition(chainDefinition);
        for (String token : filterTokens) {
            String[] nameConfigPair = toNameConfigPair(token);
            addToChain(chainName, nameConfigPair[0], nameConfigPair[1]);
        }
    }

chainName是咱們配置的過濾路徑,chainDefinition是該路徑對應的過濾器,一般咱們都是一對一的配置,好比:filterMap.put("/login", "anon");,但看到這個方法咱們知道了一個過濾路徑實際上是能夠經過傳入["filter1","filter2"...]配置多個過濾器的。在這裏會根據咱們配置的過濾路徑和過濾器映射關係一步步配置過濾器執行鏈。

public void addToChain(String chainName, String filterName, String chainSpecificFilterConfig) {
        Filter filter = getFilter(filterName);
        applyChainConfig(chainName, filter, chainSpecificFilterConfig);
        NamedFilterList chain = ensureChain(chainName);
        chain.add(filter);
    }

先從filters中根據filterName獲取對應過濾器,而後ensureChain()會先從filterChains根據chainName獲取NamedFilterList,獲取不到就建立一個並添加到filterChains而後返回。

protected NamedFilterList ensureChain(String chainName) {
        NamedFilterList chain = getChain(chainName);
        if (chain == null) {
            chain = new SimpleNamedFilterList(chainName);
            this.filterChains.put(chainName, chain);
        }
        return chain;
    }

由於過濾路徑和過濾器是一對多的關係,因此ensureChain()返回的NamedFilterList其實就是一個有着name稱屬性的List<Filter>,這個name保存的就是過濾路徑,List保存着咱們配置的過濾器。獲取到NamedFilterList後在將過濾器加入其中,這樣過濾路徑和過濾器映射關係就初始化好了。

至此,createInstance()中的createFilterChainManager()纔算執行完成,它返回了一個FilterChainManager實例。以後再將這個FilterChainManager注入PathMatchingFilterChainResolver中,它是一個過濾器執行鏈解析器。

PathMatchingFilterChainResolver中的方法很少,最爲重要的是這個getChain()方法。

public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) {
        FilterChainManager filterChainManager = getFilterChainManager();
        if (!filterChainManager.hasChains()) {
            return null;
        }
        String requestURI = getPathWithinApplication(request);
        for (String pathPattern : filterChainManager.getChainNames()) {
            if (pathMatches(pathPattern, requestURI)) {
                return filterChainManager.proxy(originalChain, pathPattern);
            }
        }
        return null;
    }

看到形參中ServletRequest和ServletResponse這兩個參數是否是感受特別親切,終於看到了點熟悉的東西了,一看就知道確定跟請求有關。是的,咱們每次請求服務器都會調用這個方法,根據請求的URL去匹配過濾器執行鏈中的過濾路徑,匹配上了就返回其對應的過濾器進行過濾。

這個方法中的filterChainManager.getChainNames()返回的是根據咱們的配置配置生成的執行鏈的過濾路徑集合,執行鏈生成的順序跟咱們的配置的順序相同。從前文中咱們也提到,在DefaultFilterChainManager的構造方法中將filterChains初始化爲一個LinkedHashMap。因此在個人Shiro筆記第一篇中提到要將範圍大的過濾器放在後面就是這個道理,若是第一個匹配的過濾路徑就是/**那後面的過濾器永遠也匹配不上。


過濾實現原理

那麼這個getChain()是如何被調用的呢?既然是HTTP請求那確定是從Tomcat過來的,當一個請求到達Tomcat時,Tomcat以責任鏈的形式調用了一系列Filter,OncePerRequestFilter就是衆多Filter中的一個。它所實現的doFilter()方法調用了自身的抽象方法doFilterInternal(),這個方法在它的子類AbstractShiroFilter中被實現了。

img

PathMatchingFilterChainResolver.getChain()就是被在doFilterInternal()中被一步步調用的調用的。

protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, 
                                    final FilterChain chain) throws ServletException, IOException {
            final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);
            final ServletResponse response = prepareServletResponse(request, servletResponse, chain);
            final Subject subject = createSubject(request, response);
            subject.execute(new Callable() {
                public Object call() throws Exception {
                    updateSessionLastAccessTime(request, response);
                    executeChain(request, response, chain);
                    return null;
                }
            });
    }

這裏先獲獲取濾器,而後執行。

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;
        FilterChainResolver resolver = getFilterChainResolver();
        if (resolver == null) {
            return origChain;
        }
        FilterChain resolved = resolver.getChain(request, response, origChain);
        if (resolved != null) {
            chain = resolved;
        } else {
        }
        return chain;
    }

經過getFilterChainResolver()就拿到了上面提到的過濾器執行鏈解析器PathMatchingFilterChainResolver,而後再調用它的getChain()匹配獲取過濾器,最終過濾器在executeChain()中被執行。


首發地址:https://www.guitu18.com/post/2019/08/01/45.html

總結

Shiro框架在URL級別的權限過濾上是基於Filter實現的。Shiro框架在咱們配置的ShiroFilterFactoryBean進行初始化調用getBean()的時候就作了不少初始化操做,將咱們配置的過濾器規則一步步添加對應的過濾器到過濾器執行鏈中,這個執行鏈最終被放入執行鏈解析器。當有請求到達Tomcat時,經過Tomcat中的Filter責任鏈執行流程,最終Shiro所定義的AbstractShiroFilter.doFilter()被執行,那麼它會去獲取執行鏈解析器,經過解析器拿到執行鏈中的過濾器並執行,這樣就實現了基於URL的權限過濾。

本文結束,這篇也算是淺入了Shiro源碼瞭解了一下Shiro過濾器的初始化以及執行過程,相比Spring的源碼Shiro的源碼要簡單易懂的不少不少,它沒有Spring那麼繞。每次看源碼的時候,我都有下面這種感受,特別是看Spring源碼的時候這種感受尤其強烈:

img

對於框架咱們所配置和調用的,永遠是浮在水面上的那一點點,不點進去,你永遠不知道下邊是一個怎樣的龐然大物。封裝的越好的框架,浮現出來的越少,隱藏的部分就越多。

好比SpringBoot,爲何能經過一個main方法就啓動一個項目,web.xml呢,application.properties呢;SpringMVC爲何就須要一個@RequestMapping就能實現從URL到方法的調用;Shiro爲何僅須要@RequiresPermissions就能實現方法級別的權限控制。

學到的越多就越是感受本身知道的越少,這是一個很矛盾卻又真實存在的感受。不說了該學習了,上面的最後一條方法級別權限控制下一篇寫,時間待定,由於最近在項目中恰好遇到數據庫優化相關問題,想先去看看MySQL。

相關文章
相關標籤/搜索