ShiroFilterFactoryBean源碼及攔截原理深刻分析前端
本篇文章篇幅比較長,可是細看下去相信對學習Shiro應該會有幫助。好了,閒話很少說,直接進入正題:web
Shiro提供了與Web集成的支持,其經過一個ShiroFilter
入口來攔截須要安全控制的URL,而後進行相應的控制,ShiroFilter相似於如Strut2/SpringMVC這種web框架的前端控制器,其是安全控制的入口點,其負責讀取配置(如ini配置文件),而後判斷URL是否須要登陸/權限等工做。spring
而要在Spring
中使用Shiro
的話,可在web.xml
中配置一個DelegatingFilterProxy
,DelegatingFilterProxy
做用是自動到Spring
容器查找名字爲shiroFilter
(filter-name
)的bean
並把全部Filter
的操做委託給它。apache
首先是在web.xml
中配置DelegatingFilterProxy
數組
<filter> <filter-name>shiroFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <init-param> <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>
配置好DelegatingFilterProxy
後,下面只要再把ShiroFilter
配置到Spring
容器(此處爲Spring
的配置文件)便可:瀏覽器
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> </bean>
能夠看到咱們使用了ShiroFilterFactoryBean
來建立shiroFilter
,這裏用到了Spring
中一種特殊的Bean——FactoryBean
。當須要獲得名爲」shiroFilter「的bean時,會調用其getObject()
來獲取實例。下面咱們經過分析ShiroFilterFactoryBean
建立實例的過程來探究Shiro是如何實現安全攔截的:緩存
public Object getObject() throws Exception { if (instance == null) { instance = createInstance(); } return instance; }
其中調用了createInstance()
來建立實例:安全
protected AbstractShiroFilter createInstance() throws Exception { // 這裏是經過FactoryBean注入的SecurityManager(必須) 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); }
能夠看到建立SpringShiroFilter
時用到了兩個組件:SecurityManager
和ChainResolver
。服務器
SecurityManager
:咱們知道其在Shiro中的地位,相似於一個「安全大管家」,至關於SpringMVC中的DispatcherServlet
或者Struts2中的FilterDispatcher
,是Shiro的心臟,全部具體的交互都經過SecurityManager
進行控制,它管理着全部Subject
、且負責進行認證和受權、及會話、緩存的管理。ChainResolver:Filter
鏈解析器,用來解析出該次請求須要執行的Filter鏈。PathMatchingFilterChainResolver
:ChainResolver
的實現類,其中還包含了兩個重要組件FilterChainManager
、PatternMatcher
FilterChainManager
:管理着Filter和Filter鏈,配合PathMatchingFilterChainResolver
解析出Filter鏈PatternMatcher
:用來進行請求路徑匹配,默認爲Ant風格的路徑匹配先有一個大致的瞭解,那麼對於源碼分析會有很多幫助。下面會對以上兩個重要的組件進行分析,包括PathMatchingFilterChainResolver
和FilterChainManager
。首先貼一段ShiroFilter
的在配置文件中的定義:session
<!-- Shiro的Web過濾器 --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager" /> <property name="loginUrl" value="/login" /> <property name="unauthorizedUrl" value="/special/unauthorized" /> <property name="filters"> <util:map> <entry key="authc" value-ref="formAuthenticationFilter" /> <entry key="logout" value-ref="logoutFilter" /> <entry key="ssl" value-ref="sslFilter"></entry> </util:map> </property> <property name="filterChainDefinitions"> <value> /resources/** = anon /plugin/** = anon /download/** = anon /special/unauthorized = anon /register = anon /login = ssl,authc /logout = logout /admin/** = roles[admin] /** = user </value> </property> </bean>
再來看看PathMatchingFilterChainResolver
和FilterChainManager
的建立過程:
protected FilterChainManager createFilterChainManager() { // 默認使用的FilterChainManager是DefaultFilterChainManager DefaultFilterChainManager manager = new DefaultFilterChainManager(); // DefaultFilterChainManager默認會註冊的filters(後面會列出) Map<String, Filter> defaultFilters = manager.getFilters(); // 將ShiroFilterFactoryBean配置的一些公共屬性(上面配置的loginUrl,successUrl,unauthorizeUrl)應用到默認註冊的filter上去 for (Filter filter : defaultFilters.values()) { applyGlobalPropertiesIfNecessary(filter); } // 處理自定義的filter(上面配置的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); } // 將Filter添加到manager中去,能夠看到對於Filter的管理是依賴於FilterChainManager的 manager.addFilter(name, filter, false); } } // 根據FilterChainDefinition的配置來構建Filter鏈(上面配置的filterChainDefinitions屬性) 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(); // 後面會分析該步的源碼,功能上就是建立Filter鏈 manager.createChain(url, chainDefinition); } } return manager; }
下面有必要來看看DefaultFilterChainManager
的源碼,分析一下上面調用到的方法。先來看看他的幾個重要的屬性:
private FilterConfig filterConfig; private Map<String, Filter> filters; //pool of filters available for creating chains private Map<String, NamedFilterList> filterChains; //key: chain name, value: chain
其中filterConfig
僅在初始化Filter時有效,而咱們自定義的Filter都不是init的,因此該屬性能夠暫時忽略()。
然後面兩張map就重要了:filters中緩存了全部添加的filter
,filterChains則緩存了全部的filterChain
。其中前者的key是filter name,value是Filter
。然後者的key是chain name,value是NamedFilterList
。
有的童鞋可能會問NamedFilterList
是怎麼樣的結構呢,你能夠把它當成List<Filter>
,這樣就好理解了吧。下面再分析剛纔createFilterChainManager()
中調用過的manager
的幾個方法:
public void addFilter(String name, Filter filter, boolean init) { addFilter(name, filter, init, true); } protected void addFilter(String name, Filter filter, boolean init, boolean overwrite) { Filter existing = getFilter(name); if (existing == null || overwrite) { if (filter instanceof Nameable) { ((Nameable) filter).setName(name); } if (init) { initFilter(filter); } this.filters.put(name, filter); } }
將filter
緩存到filters
這張map
裏,不論是默認註冊的仍是自定義的都須要FilterChainManager
來統一管理。
// chainName就是攔截路徑"/resources/**",chainDefinition就是多個過濾器名的字符串 public void createChain(String chainName, String chainDefinition) { if (!StringUtils.hasText(chainName)) { throw new NullPointerException("chainName cannot be null or empty."); } if (!StringUtils.hasText(chainDefinition)) { throw new NullPointerException("chainDefinition cannot be null or empty."); } if (log.isDebugEnabled()) { log.debug("Creating chain [" + chainName + "] from String definition [" + chainDefinition + "]"); } // 先分離出配置的各個filter,好比 // "authc, roles[admin,user], perms[file:edit]" 分離後的結果是: // { "authc", "roles[admin,user]", "perms[file:edit]" } String[] filterTokens = splitChainDefinition(chainDefinition); // 進一步分離出"[]"內的內容,其中nameConfigPair是一個長度爲2的數組 // 好比 roles[admin,user] 通過解析後的nameConfigPair 爲{"roles", "admin,user"} for (String token : filterTokens) { String[] nameConfigPair = toNameConfigPair(token); // 獲得了 攔截路徑、filter以及可能的"[]"中的值,那麼執行addToChain addToChain(chainName, nameConfigPair[0], nameConfigPair[1]); } }
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)."); } // 將"[]"中的匹配關係註冊到filter中 applyChainConfig(chainName, filter, chainSpecificFilterConfig); // 確保chain已經被加到filterChains這張map中了 NamedFilterList chain = ensureChain(chainName); // 將該filter加入當前chain chain.add(filter); }
至此,FilterChainManager
就建立完了,它無非就是緩存了兩張map
,沒有什麼邏輯上的操做。下面將FilterChainManager
設置到PathMatchingFilterChainResolver
中。PathMatchingFilterChainResolver
實現了FilterChainResolver
接口,該接口中只定義了一個方法:
FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain);
經過解析請求來獲得一個新的FilterChain
。而PathMatchingFilterChainResolver
實現了該接口,依靠了FilterChainManager
中保存的chainFilters
和filters
這兩張map來根據請求路徑解析出相應的filterChain
,而且和originalChain
組合起來使用。下面具體看看PathMatchingFilterChainResolver
中的實現:
public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) { // 獲得 FilterChainManager FilterChainManager filterChainManager = getFilterChainManager(); if (!filterChainManager.hasChains()) { return null; } String requestURI = getPathWithinApplication(request); // chainNames就是剛定義的filterChains的keySet,也就是全部的路徑集合(好比:["/resources/**","/login"]) for (String pathPattern : filterChainManager.getChainNames()) { // 請求路徑是否匹配某個 定義好的路徑: if (pathMatches(pathPattern, requestURI)) { if (log.isTraceEnabled()) { log.trace("Matched path pattern [" + pathPattern + "] for requestURI [" + requestURI + "]. " + "Utilizing corresponding filter chain..."); } // 找到第一個匹配的Filter鏈,那麼就返回一個ProxiedFilterChain return filterChainManager.proxy(originalChain, pathPattern); } } return null; }
這裏返回只有兩種狀況,要麼是null
,要麼就是一個ProxiedFilterChain
。返回null
並不表示中斷FilterChain
,而是隻用originChain
。而關於ProxiedFilterChain
,它實現了FilterChain
,內部維護了兩份FilterChain
(其實一個是FilterChain
,另外一個是List<Filter>
)
FilterChain
也就是web.xml
中註冊的Filter
造成的FilterChain
,咱們稱之爲originChain
。而另外一個List<Filter>
則是咱們在Shiro中註冊的Filter鏈了,下面看看ProxiedFilterChain
中關於doFilter(...)
的實現:
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { if (this.filters == null || this.filters.size() == this.index) { //we've reached the end of the wrapped chain, so invoke the original one: if (log.isTraceEnabled()) { log.trace("Invoking original filter chain."); } this.orig.doFilter(request, response); } else { if (log.isTraceEnabled()) { log.trace("Invoking wrapped filter at index [" + this.index + "]"); } this.filters.get(this.index++).doFilter(request, response, this); } }
能夠看到,它會先執行Shiro中執行的filter
,而後再執行web.xml
中的Filter
。不過要注意的是,須要等到originChain
執行到ShiroFilter
以後纔會執行Shiro中的Filter鏈。
至此,兩個組件的建立過程差很少都介紹完了,那麼當這兩個組件建立完畢後,是如何工做的呢?
先從ShiroFilter
入手,由於它是總的攔截器,看看其中的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); //noinspection unchecked subject.execute(new Callable() { public Object call() throws Exception { // 其實須要關心的就在這裏 // touch一下session updateSessionLastAccessTime(request, response); // 執行Filter鏈 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; } //otherwise it's not one of the two exceptions expected by the filter method signature - wrap it in one: String msg = "Filtered request failed."; throw new ServletException(msg, t); } }
跟進executeChain(...)
方法:
protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain) throws IOException, ServletException { FilterChain chain = getExecutionChain(request, response, origChain); chain.doFilter(request, response); }
如何獲得FilterChain
的呢?若是你認真的看到這裏,那麼你應該不難想到其中確定利用了剛纔註冊的ChainResolver
:
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; }
猜對了~而且也驗證了當resolver.getChain(...)
返回null
時,直接使用originChain
了。而後執行返回的FilterChain
的doFilter(...)
方法。這個過程咱們再脫離代碼來分析一下:當咱們從瀏覽器發出一個請求,究竟發生了什麼?
這裏只站在Filter
的層面來分析。服務器啓動後,讀取web.xml
中的filter
、filter-mapping
節點後組成FilterChain
,對請求進行攔截。攔截的順序按照filter節點的定義順序,Shiro利用ShiroFilter
來充當一個總的攔截器來分發全部須要被Shiro攔截的請求,因此咱們看到在Shiro中咱們還能夠自定義攔截器。ShiroFilter
根據它在攔截器中的位置,只要執行到了那麼就會暫時中斷原FilterChain
的執行,先執行Shiro中定義的Filter
,最後再執行原FilterChian
。能夠打個比方,好比說原本有一條鐵鏈,一直螞蟻從鐵鏈的開端往末端爬,其中某一環叫ShiroFilter
,那麼當螞蟻爬到ShiroFilter
這一環時,將鐵鏈打斷,而且接上另外一端鐵鏈(Shiro中自定義的Filter
),這樣就構成了一條新的鐵鏈。而後螞蟻繼續爬行(後續的執行過程)。
最後附上默認註冊的filters:
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); }
水平有限,寫得蠻不容易,看源碼加寫花了整整2天。但願對你們能有幫助~