Shiro的Filter機制詳解---原始碼分析
Shiro的Filter機制詳解
首先從spring-shiro.xml的filter配置說起,先回答兩個問題:
1, 為什麼相同url規則,後面定義的會覆蓋前面定義的(執行的時候只執行最後一個)。
2, 為什麼兩個url規則都可以匹配同一個url,只執行第一個呢。
下面分別從這兩個問題入手,最終閱讀原始碼得到解答。
問題一解答
相同url但定義在不同的行,後面覆蓋前面
如
/usr/login.do=test3 /usr/login.do=test1,test2 不會執行test3的filter
要解答第一個問題,需要知道shiro(或者說是spring)是如何掃描這些url規則並儲存的。
Web.xml配置shiro以及spring-shiro.xml的核心配置
在web.xml中定義shiroFilter
<filter> <filter-name>shiroFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <init-param> <!-- 該值預設為false,表示生命週期由SpringApplicationContext管理,設定為true則表示由ServletContainer管理 --> <param-name>targetFilterLifecycle</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>shiroFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
在spring-shiro.xml中定義shiroFilter
(要和web.xml中的名稱一樣,因為spring就是依靠名稱來獲取這個bean的)
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager" /> <property name="loginUrl" value="/login.jsp" /> <property name="unauthorizedUrl" value="/WEB-INF/405.html" /> <property name="filters"> <map> <entry key="kickout" value-ref="kickoutSessionControlFilter" /> </map> </property> <property name="filterChainDefinitions"> <value> /**=kickout /usr/login.do=anon /security/*=anon /usr/login.do=authc /usr/test/*=authc </value> </property> </bean>
都定義好之後,分析org.springframework.web.filter.DelegatingFilterProxy發現該filter類的任務是:將具體工作分派給org.apache.shiro.spring.web.ShiroFilterFactoryBean這個類中的靜態內部類SpringShiroFilter做。
具體spring內部是怎麼將工作委派的,暫時沒有分析。
現在關注的是當spring把具體工作委派給ShiroFilterFactoryBean後,該類是怎麼工作的。
Spring將配置注入到ShiroFilterFactoryBean
在這之前,spring通過bean注入,將ShiroFilterFactoryBean的相關成員通過set方法注入進去。
前面已經配置了filters和filterChainDefinitions,再次貼出如下所示:
<property name="filters"> <map> <entry key="kickout" value-ref="kickoutSessionControlFilter" /> </map> </property> <property name="filterChainDefinitions"> <value> /**=kickout /usr/login.do=anon /security/*=anon /usr/login.do=authc /usr/test/*=authc </value> </property>
看一下ShiroFilterFactoryBean是怎麼接收他們的。
Filters很簡單,只需要map接收就自動完成了。
public void setFilters(Map<String, Filter> filters) { this.filters = filters; }
但是filterChainDefinitions是String型別的,需要轉換(使用了ini轉換方法,內部使用LinkedHashMap儲存url和filter的對映關係,保證了順序)
public void setFilterChainDefinitions(String definitions) { Ini ini = new Ini(); ini.load(definitions); //did they explicitly state a 'urls' section? Not necessary, but just in case: Ini.Section section = ini.getSection(IniFilterChainResolverFactory.URLS); if (CollectionUtils.isEmpty(section)) { //no urls section. Since this _is_ a urls chain definition property, just assume the //default section contains only the definitions: section = ini.getSection(Ini.DEFAULT_SECTION_NAME); } setFilterChainDefinitionMap(section); }
這兩步完成後,filters被注入
filterChianDefinitions也被注入,但是注入方法通過shiro自定義了ini方式,
該方式通過LinkedHashMap儲存url規則和對應的許可權(鍵值對),所以當寫了相同的url規則,後者會覆蓋前者(------現在對HashMap的儲存規則遺忘了,需要再看一下)
問題一解答完成
問題二解答:
同一個url可以匹配不同的規則,但只執行首行 /usr/* =test1,test2 /usr/login.do=test3 url = /usr/login.do請求來了,不會執行test3,因為已經匹配了/usr/* =test1,test2 要解答該問題,需要知道每個url的FilterChain是如何獲取的
接上分析:
有了filter和filterChainDefinitionMap的資料後,下面的工作是構造FilterChainManager
構造FilterChainManager
為什麼到這一步呢?
檢視spring委託機制,最終找到ShiroFilterFactoryBean的createInstance()方法(這個方法是shiro的filter構造機制的主線),由於ShiroFilterFactoryBean 實現了FactoryBean,spring就是通過這個介面的createInstance方法獲取到filter例項的,下面是該方法在ShiroFilterFactoryBean中的實現:
protected AbstractShiroFilter createInstance() throws Exception { log.debug("Creating Shiro Filter instance."); SecurityManager securityManager = getSecurityManager(); if (securityManager == null) { String msg = "SecurityManager property must be set."; throw new BeanInitializationException(msg); } if (!(securityManager instanceof WebSecurityManager)) { String msg = "The security manager does not implement the WebSecurityManager interface."; throw new BeanInitializationException(msg); } FilterChainManager manager = createFilterChainManager(); PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver(); chainResolver.setFilterChainManager(manager); return new SpringShiroFilter((WebSecurityManager) securityManager, chainResolver); }
從這裡可以知道,首先獲取filterChainManager,具體方法如下
protected FilterChainManager createFilterChainManager() { DefaultFilterChainManager manager = new DefaultFilterChainManager(); Map<String, Filter> defaultFilters = manager.getFilters(); //apply global settings if necessary: for (Filter filter : defaultFilters.values()) { applyGlobalPropertiesIfNecessary(filter); } //Apply the acquired and/or configured filters: 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); } //'init' argument is false, since Spring-configured filters should be initialized //in Spring (i.e. 'init-method=blah') or implement InitializingBean: manager.addFilter(name, filter, false); } } //build up the chains: 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; }
分析後得知,首先在createFilterChainManager()方法中,建立一個DefaultFilterChainManager物件,而這個物件的建構函式在最後會將DefaultFilter中定義的shiro預設的filter對映加入到該物件中。如下程式碼就是DefaultFilter的定義。
在DefaultFilterChainManager中還做了一件事就是url-filter的對映變成filterChain,這句程式碼就是執行這個任務(將我們在xml檔案中定義的filterChainDefinitions變成filterChain)。
manager.createChain(url, chainDefinition);
作用是將許可權分割:如
"authc, roles[admin,user], perms[file:edit]"
將會被分割為
{ "authc", "roles[admin,user]", "perms[file:edit]" }
具體的原始碼如下:
public void createChain(String chainName, String chainDefinition) { //。。。。。。。。 //parse the value by tokenizing it to get the resulting filter-specific config entries // //e.g. for a value of // // "authc, roles[admin,user], perms[file:edit]" // // the resulting token array would equal // // { "authc", "roles[admin,user]", "perms[file:edit]" } // String[] filterTokens = splitChainDefinition(chainDefinition); for (String token : filterTokens) { String[] nameConfigPair = toNameConfigPair(token); addToChain(chainName, nameConfigPair[0], nameConfigPair[1]); } }
並且通過toNameConfigPair(token)將如:roles[admin,user]形式的變成roles,admin,user形式的分割
然後根據url規則 對映 許可權和角色
可以發現,每次分割一個token,都會通過addToChain方法接受
分析public void addToChain(String chainName, String filterName, String chainSpecificFilterConfig)方法
public void addToChain(String chainName, String filterName, String chainSpecificFilterConfig) { if (!StringUtils.hasText(chainName)) { throw new IllegalArgumentException("chainName cannot be null or empty."); } Filter filter = getFilter(filterName); if (filter == null) { throw new IllegalArgumentException("There is no filter with name '" + filterName + "' to apply to chain [" + chainName + "] in the pool of available Filters. Ensure a " + "filter with that name/path has first been registered with the addFilter method(s)."); } applyChainConfig(chainName, filter, chainSpecificFilterConfig); NamedFilterList chain = ensureChain(chainName); chain.add(filter); }
分析applyChainConfig(chainName, filter, chainSpecificFilterConfig);
protected void applyChainConfig(String chainName, Filter filter, String chainSpecificFilterConfig) { //…………………………. if (filter instanceof PathConfigProcessor) { ((PathConfigProcessor) filter).processPathConfig(chainName, chainSpecificFilterConfig); } else { if (StringUtils.hasText(chainSpecificFilterConfig)) { //they specified a filter configuration, but the Filter doesn't implement PathConfigProcessor //this is an erroneous config: String msg = "chainSpecificFilterConfig was specified, but the underlying " + "Filter instance is not an 'instanceof' " + PathConfigProcessor.class.getName() + ". This is required if the filter is to accept " + "chain-specific configuration."; throw new ConfigurationException(msg); } } }
由於我們自定義的filter都是PathMatchingFilter的子類,所以在applyChainConfig方法中完成的就是將url新增到filter的url表中。
在PathMatchingFilter中可以發現
protected Map<String, Object> appliedPaths = new LinkedHashMap<String, Object>();
processPathConfig 方法的實現如下
public Filter processPathConfig(String path, String config) { String[] values = null; if (config != null) { values = split(config); } this.appliedPaths.put(path, values); return this; }
基本上在spring-shiro.xml中定義filter的載入過程已經閱讀完成,
1, 定義一個DefaultFilterChainManager物件
2, 首先載入預設的filter
3, 載入xml檔案中定義的filter
4, 載入xml檔案定義的url和filter對映關係
5, 將對映關係解析為以url為鍵,NamedFilterList為值的鍵值對。
6, 在解析的過程中,對每個url和對應的過濾條件,都會放到對應filter的appliedPaths中(在PathMatchingFilter中定義)。
現在FilterChainManager的物件已經建立完畢,並且每個filter也已經例項化完畢。
構造SpingShiroFilter
在建立SpringShiroFilter之前還要將剛才建立的FilterChainManager物件包裝成一個PathMatchingFilterChainResolver物件(註釋的意思是:不直接將FilterChainManager物件暴露給AbstractShiroFilter的實現者,在這裡就是SpringShiroFilter。)
PathMatchingFilterChainResolver最重要的作用是:當請求url來的時候,他擔任匹配工作(呼叫該類的getChain方法做匹配,暫時先不分析該方法,等知道在哪裡呼叫該方法時候再分析。其實問題二此時已經可以解答,通過該方法就可以知道,某個url匹配到過濾鏈的第一個規則時就return了。)
上圖最後一句話執行完成後,一個SpringShiroFilter建立完畢。
請求過濾過程分析(上)
下面分析當url請求到來的時候,shiro是如何完成過濾的。首先通過圖片大致的瞭解一下。
現在分析AbstractShiroFilter的doFilterInternal()方法
protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain) throws ServletException, IOException { Throwable t = null; try { 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; } }); } catch (ExecutionException ex) { t = ex.getCause(); } catch (Throwable throwable) { t = throwable; } //………… }
暫時不關心subject相關的建立等過程,只關心這行程式碼
executeChain(request, response, chain);
具體實現如下
protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain) throws IOException, ServletException { FilterChain chain = getExecutionChain(request, response, origChain); chain.doFilter(request, response); }
再看getExecutionChain(request, response, origChain);具體實現如下:
protected FilterChain getExecutionChain(ServletRequest request, ServletResponse response, FilterChain origChain) { FilterChain chain = origChain; FilterChainResolver resolver = getFilterChainResolver(); if (resolver == null) { log.debug("No FilterChainResolver configured. Returning original FilterChain."); return origChain; } 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; }
可以發現,這裡用到了我們在建立SpringShiroFilter時傳遞的FilterChainResolver,至此,我們終於找到了getChain()方法在這裡被呼叫了。其原始碼實現如下
public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) { FilterChainManager filterChainManager = getFilterChainManager(); if (!filterChainManager.hasChains()) { return null; } String requestURI = getPathWithinApplication(request); //the 'chain names' in this implementation are actually path patterns defined by the user. We just use them //as the chain name for the FilterChainManager's requirements for (String pathPattern : filterChainManager.getChainNames()) { // If the path does match, then pass on to the subclass implementation for specific checks: if (pathMatches(pathPattern, requestURI)) { if (log.isTraceEnabled()) { log.trace("Matched path pattern [" + pathPattern + "] for requestURI [" + requestURI + "]. " + "Utilizing corresponding filter chain..."); } return filterChainManager.proxy(originalChain, pathPattern); } } return null; }
從for迴圈可以看出,當匹配到第一個url規則,則return一個代表這個url規則的FilterChain給web容器執行。
問題二解答:每個url在匹配他的FilterChain時,當匹配到第一個URL規則時,就返回。
請求過濾過程分析(下)
FilterChain的實現類為org.apache.shiro.web.servlet.ProxiedFilterChain
從該類的doFilter方法可以知道,它會將Filter鏈的Filter的doFilter方法順序執行一遍。下圖展示了這一過程
現在只需要分析每個Filter的doFilter方法就行了。
上面是它的繼承關係:最終的doFilter方法在OncePerRequestFilter中實現,具體程式碼如下:
public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException { String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName(); if ( request.getAttribute(alreadyFilteredAttributeName) != null ) { log.trace("Filter '{}' already executed. Proceeding without invoking this filter.", getName()); filterChain.doFilter(request, response); } else //noinspection deprecation if (/* added in 1.2: */ !isEnabled(request, response) || /* retain backwards compatibility: */ shouldNotFilter(request) ) { log.debug("Filter '{}' is not enabled for the current request. Proceeding without invoking this filter.", getName()); filterChain.doFilter(request, response); } else { // Do invoke this filter... log.trace("Filter '{}' not yet executed. Executing now.", getName()); request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE); try { doFilterInternal(request, response, filterChain); } finally { // Once the request has finished, we're done and we don't // need to mark as 'already filtered' any more. request.removeAttribute(alreadyFilteredAttributeName); } } }
可以發現該方法最終會呼叫doFilterInternal(request, response, filterChain);來完成具體的過濾操作,doFilterInternal方法在 SpringShiroFilter的直接父類AbstractShiroFilter的具體實現過程已經在上面分析過了:具體的就是shiro真正驗證授權前的subject,session等初始化的工作,使得後面的過濾以及驗證授權工作可以得到subject等然後正常工作。完成後呼叫其他shiro filter進行繼續過濾
而除了shiroFilter之外,其餘的filter都是AdviceFilter分支的子類。剛才看了AbstractShiroFilter的doFilterInternal方法,現在看一下AdviceFilter對該方法的實現:
public void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException { Exception exception = null; try { boolean continueChain = preHandle(request, response); if (log.isTraceEnabled()) { log.trace("Invoked preHandle method. Continuing chain?: [" + continueChain + "]"); } if (continueChain) { executeChain(request, response, chain); } postHandle(request, response); if (log.isTraceEnabled()) { log.trace("Successfully invoked postHandle method"); } } catch (Exception e) { exception = e; } finally { cleanup(request, response, exception); } }
與AbstractShiroFilter的doFilterInternal方法不同的是,這裡通過continueChain變數來判斷到底後續的filter會不會被繼續執行。而該變數的值由preHandle()函式決定。
基本上所有在系統中用到的filter都是繼承PathMatchingFilter類的。看一下該類的preHandle()函式實現,可以發現,我們在xml檔案中定義的url匹配,在這裡面可以看到匹配原則了:
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception { if (this.appliedPaths == null || this.appliedPaths.isEmpty()) { if (log.isTraceEnabled()) { log.trace("appliedPaths property is null or empty. This Filter will passthrough immediately."); } return true; } for (String path : this.appliedPaths.keySet()) { // If the path does match, then pass on to the subclass implementation for specific checks //(first match 'wins'):相關推薦
Shiro的Filter機制詳解---原始碼分析
Shiro的Filter機制詳解 首先從spring-shiro.xml的filter配置說起,先回答兩個問題: 1, 為什麼相同url規則,後面定義的會覆蓋前面定義的(執行的時候只執行最後一個)。 2, 為什麼兩個url規則都可以匹配同一個url,只執行第一個呢。 下面分別從這兩個問題入手,最終閱讀原
《Exploring in UE4》多線程機制詳解[原理分析]
ise The nor tegra 創建方式 destroy 解釋 我們 star 轉自:https://zhuanlan.zhihu.com/c_164452593 目錄一.概述二."標準"多線程三.AsyncTask系統3.1 FQueuedThreadPool線程池
《Exploring in UE4》多執行緒機制詳解[原理分析]
目錄 一.概述 二."標準"多執行緒 三.AsyncTask系統 3.1 FQueuedThreadPool執行緒池 3.2 Asyntask與IQueuedWork 3.3 其他相關技術細節 四.TaskGraph系統 4.1 從Tick函式談起 4.2 T
ArrayList詳解-原始碼分析
# ArrayList詳解-原始碼分析 ### 1. 概述 在平時的開發中,用到最多的集合應該就是ArrayList了,本篇文章將結合原始碼來學習ArrayList。 - **ArrayList是基於陣列實現的集合列表** - **支援任意性的訪問(可根據索引直接得到你想要的元素)** - **執行緒不
LinkedList詳解-原始碼分析
# LinkedList詳解-原始碼分析 LinkedList是List介面的第二個具體的實現類,第一個是ArrayList,前面一篇文章已經總結過了,下面我們來結合原始碼,學習LinkedList。 - **基於雙向連結串列實現** - **便於插入和刪除,不便於遍歷** - **非執行緒安全** -
Android非同步訊息處理機制詳解及原始碼分析
PS一句:最終還是選擇CSDN來整理髮表這幾年的知識點,該文章平行遷移到CSDN。因為CSDN也支援MarkDown語法了,牛逼啊! 【工匠若水 http://blog.csdn.net/yanbober 轉載煩請註明出處,尊重分享成果】 最近相對來說比較閒,加上養病,所
Android觸控式螢幕事件派發機制詳解與原始碼分析三(Activity篇)
PS一句:最終還是選擇CSDN來整理髮表這幾年的知識點,該文章平行遷移到CSDN。因為CSDN也支援MarkDown語法了,牛逼啊! 【工匠若水 http://blog.csdn.net/yanbober】 該篇承接上一篇《Android觸控式螢幕事件派發機制詳解與原始碼分析
Android觸控式螢幕事件派發機制詳解與原始碼分析二(ViewGroup篇)
【工匠若水 http://blog.csdn.net/yanbober】 該篇承接上一篇《Android觸控式螢幕事件派發機制詳解與原始碼分析一(View篇)》,閱讀本篇之前建議先閱讀。當然,閱讀完這一篇之後可以閱讀繼續進階的下一篇《Android觸控式螢幕事件派發機制詳解與原始碼
Android觸控式螢幕事件派發機制詳解與原始碼分析一(View篇)
【工匠若水 http://blog.csdn.net/yanbober】 Notice:閱讀完該篇之後如果想繼續深入閱讀Android觸控式螢幕事件派發機制詳解與原始碼分析下一篇請點選《Android觸控式螢幕事件派發機制詳解與原始碼分析二(ViewGroup篇)》檢視。 1
Java反射機制詳解一
java 反射 反射機制 工廠模式 1反射機制是什麽反射機制是在運行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個對象,都能夠調用它的任意一個方法和屬性;這種動態獲取的信息以及動態調用對象的方法的功能稱為java語言的反射機制。在面向對象的世界裏,萬事萬物皆對象.在ja
Java垃圾回收(GC)機制詳解
nbsp 引用計數 維護 png 對象 最新 新的 com 前沿 垃圾回收算法有兩種,根據不同的虛擬機策略不同 1、引用計數法 2、可達性分析法 由於我們平常使用的hotspot虛擬機用的是第二種。 那哪些是可達的呢? 這個算法的基本思想是通過一系列稱為“GC Roots”
Java的內存回收機制詳解
out 結果 int destroy pan 得出 ida public toc http://blog.csdn.net/mengern/article/details/38150431 Java中提供了垃圾強制回收機制的方法System.gc(),但是系統並不保證會立即
Java反射機制詳解
ride length 數組大小 conf array arraycopy 動態調用 ray info Java反射機制詳解 |目錄 1反射機制是什麽 2反射機制能做什麽 3反射機制的相關API ·通過一個對象獲得完整的包名和類名 ·實例化Class類對象 ·獲
Cookie/Session機制詳解
order 隱藏對象 tro 這就是 緩存 cat 時域 共享 創建 會話(Session)跟蹤是Web程序中常用的技術,用來跟蹤用戶的整個會話。常用的會話跟蹤技術是Cookie與Session。Cookie通過在客戶端記錄信息確定用戶身份,Session通過在服務器端記錄
(轉)java的動態代理機制詳解
spring throw system urn log enc before 代理類 三個參數 原文出自:http://www.cnblogs.com/xiaoluo501395377/p/3383130.html 在學習Spring的時候,我們知道Spring主要
JVM類加載機制詳解(一)JVM類加載過程
進行 虛擬機啟動 類加載的時機 bsp 參與 tro ext 環境 java代碼 首先Throws(拋出)幾個自己學習過程中一直疑惑的問題: 1、什麽是類加載?什麽時候進行類加載? 2、什麽是類初始化?什麽時候進行類初始化? 3、什麽時候會為變量分配內存? 4、什麽時候會為
Java必知必會:異常機制詳解
賦值 輸出結果 類庫 負數 虛擬 類名 通過反射 基於 all 一、Java異常概述 在Java中,所有的事件都能由類描述,Java中的異常就是由java.lang包下的異常類描述的。 1、Throwable(可拋出):異常類的最終父類,它有兩個子類,Error與Exce
Java類加載機制詳解
package itl prot 啟動 bool ddc 發現 很多 har 一、類加載器 類加載器(ClassLoader),顧名思義,即加載類的東西。在我們使用一個類之前,JVM需要先將該類的字節碼文件(.class文件)從磁盤、網絡或其他來源加載到內存中,並對字節碼進
Java的反射機制詳解(一)
pbc spa 詳解 uno face target lan tor cin 8n72q傅釁8戰sig叢http://www.docin.com/app/user/userinfo?userid=179185461 8u炊3F7LB椒1http://huiyi.docin.
java的動態代理機制詳解
following space h264 owin ipc ava smr hot lower Oq耗喊都自稚剿8斷0ohttp://shequ.docin.com/txqq_073ec59204 掀承U智泛纖06劣z粕05寡http://www.facebolw.com