(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服務介面整合實現自定義的登入與許可權的邏輯。