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天。但愿对你们能有帮助~