1. 程式人生 > >(springboot)shiro安全框架自定義過濾器出現的幾個疑難雜症解決方案

(springboot)shiro安全框架自定義過濾器出現的幾個疑難雜症解決方案

問題一:多次重複重定向問題(匹配多個過濾器鏈重複呼叫其對應過濾器)

問題二:shiro認證時Realm會執行兩次

在使用springboot框架整合shiro安全認證框架時踩了很多坑,每次出問題網上都找不到其中的解決方案,這裡貼兩個我遇到的坑,以及其解決方案給大家,希望大家可以少走彎路。

問題一場景:

		// 自定義攔截器
		Map<String, Filter> customisedFilter = new HashMap<>();
		customisedFilter.put("url", new CustomRolesAuthorizationFilter());

		// 配置對映關係
		filterChainDefinitionMap.put("/login", "anon");
		filterChainDefinitionMap.put("/index", "anon");
		filterChainDefinitionMap.put("/unauthorized", "anon");
		filterChainDefinitionMap.put("/doLogout", "logout");
		filterChainDefinitionMap.put("/**", "url");
		shiroFilterFactoryBean.setFilters(customisedFilter);
		shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

當我訪問/login時,會進入anon過濾器 。但是問題出在,他還會額外繼續執行url(自定義過濾器),在自定義過濾器中的邏輯是使用者沒有登入就重定向到/login這個url去,然後又進入anon過濾器,又執行url過濾器,又進行重定向,迴圈往復造成多次重定向。

問題一解決過程:

翻閱了一些shiro的資料,瞭解了一下其中過濾器的機制,shiro會對Servlet容器裡的FilterChain進行代理,Shiro會通過一個代理類ProxiedFilterChain對Servlet對其進行代理,其中會先進行Shiro中使用者自己配置的攔截器配置對映的關係,即先走對映關係中匹配到的url對應的過濾器,然後會去執行Servlet中的FilterChain進行Filter鏈的執行。

如果有看過底層原始碼,就會看到一個originalChain這個名詞,它就是Servlet儲存的FilterChain。也就是說,每次請求都將會先走Shiro的過濾器鏈,然後再走Servlet的過濾器鏈。

這裡我是SpringBoot框架,可以看到控制檯在專案啟動時自動配置characterEncodingFilter、requestContextFilter等等一些預設過濾器,把它加入一個叫FilterRegistrationBean中,作為一個過濾器鏈。可以看到紅框中,這兩個是我自己自定義的過濾器,我將其配置到Shiro中作為Shiro過濾器鏈使用,但沒想到SpringBoot自動把這兩個過濾器配置到了FilterRegistrationBean中,並且路徑為/*,這也就能理解為什麼上面會匹配到anon過濾器之後還會往下執行到我們的自定義過濾器了。

這是我兩個自定義過濾器的配置Bean,其中過濾器實現了PathMatchingFilter介面,我不知道是不是因為這個,還是什麼奇怪的原因才會把它載入到FilterRegistrationBean中,如果有知道為什麼的可以在底下評論告知我一下,至今還是對此摸不著頭腦。

問題一解決方案:

最後我把這兩個Bean註釋掉了,在配置Shiro中我直接new出來,不作為Bean交給IOC管理了,這樣就解決了問題,Shiro就不會呼叫額外的自定義過濾器了。

問題二解決過程:

當時因為我需要動態配置對映關係,會從資料庫中讀取需要對映的url與需要攔截的角色與許可權,過濾器鏈有可能同一個URL匹配多個過濾器(例如permission過濾器和roles過濾器,角色與許可權雙驗證效果),所以我就自定義了PathMatchingFilterChainResolver,並重寫了getChain方法,這個方法是用來匹配返回即將呼叫的過濾器的,在裡面我的邏輯是匹配所有匹配到的URL,把所有對應能匹配到的過濾器全部執行,但錯誤在,我在最底下寫了一個匹配規則 /** 匹配authc,邏輯是除了需要許可權驗證、或是設定了anon、logout過濾器以外的url都要進行登入才可以訪問,這就造成了在進行登入的時候,訪問一次登入方法,又會去匹配那個/** 繼續走一次身份驗證,而走身份驗證 subject.login 的底層是會走Realm,這就造成了走兩次Realm。

問題二解決方案:

造成這種問題很大可能是PathMatchingFilterChainResolver中的getChain邏輯沒有寫好,ufl對映關係配置不科學造成。

下面貼一個我的getChian方法。

    public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) {
        FilterChainManager filterChainManager = getFilterChainManager();
        if (!filterChainManager.hasChains()) {
            return null;
        }

        String requestURI = getPathWithinApplication(request);
        
        //要執行的過濾器集合
        List<String> chainNames = new ArrayList<String>();

        //獲取全部的攔截url
        Set<String> chain = filterChainManager.getChainNames();

        for (String pathPattern : chain){
            // 匹配所有匹配到的url,裝入chainNames作為即將要執行的過濾器集合
            if (pathMatches(pathPattern, requestURI)) {
                chainNames.add(pathPattern);
            }
        }
        //攔截器鏈的最後一個Url,因為它匹配全部的url,這裡不與其他攔截器衝突,剔除/**url的匹配
        Object lastUrl = "/**";
        chainNames.remove(lastUrl);
        //沒有匹配到url
        if(chainNames.size() == 0) {
            //為了不與其他攔截器衝突,在全部url都不匹配的情況下才匹配/**
            chainNames.add("/**");
        }
        return customDefaultFilterChainManager.proxy(originalChain, chainNames);
    }

基本思路就是匹配除了/**的所有過濾器路徑,如果能匹配到,則執行匹配到的過濾器集合,如果一個都沒匹配到,才走最後的身份驗證。

要了解底層Shiro是怎麼進行身份驗證,這樣出了問題就能通過Debug進行排錯,如果專案中需要深度整合Shiro,改寫很多Shiro的驗證邏輯,需要了解Shiro底層原理,如FilterChainResolver(處理匹配規則)、FilterChainManager(處理匹配到的過濾器鏈)、PathMatchingFilter(自定義過濾器)、AuthorizingRealm(獲取認證相關資料來源的地方)、CredentialsMatcher(自定義登入認證邏輯)等等的原理,才能靈活運用Shiro框架。有空寫一篇動態URL配置,動態雙攔截角色許可權的Shiro配置,和與SSO單點登入的WebService服務介面整合實現自定義的登入與許可權的邏輯。