ShiroFilterFactoryBean源码分析
shiro是一个安全框架,集验证、授权、会话管理以及加密功能,并且可以Spring集成,本文主要是针对在web环境下shiro和spring集成的基础下分析ShiroFilterFactoryBean。
废话不多说了,先上配置图
首先在web.xml配置shiroFilter, DelegatingFilterProxy类的作用是把shiroFilter的操作委托给spring
<filter> <filter-name>shiroFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <init-param> <!-- true为bean的生命周期由ServletContainer管理,false为SpringApplicationContext管理 --> <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中配置shiroFilter bean
<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">
<!-- 自定义Filter --> <util:map> <entry key="authc" value-ref="formAuthenticationFilter" /> </util:map> </property> <property name="filterChainDefinitions"> <value> /resources/** = anon /login = ssl,authc /** = user </value> </property> </bean>
这样,shiro和spring的集成就基本配置好了,securityManager的配置现在暂时不分析,现在我们重点来分析下源码。
从配置文件中我们可以看到,我们使用了ShiroFilterFactoryBean创建shiroFilter,来看看ShiroFilterFactoryBean:
public class ShiroFilterFactoryBean implements FactoryBean, BeanPostProcessor { private static transient final Logger log = LoggerFactory.getLogger(ShiroFilterFactoryBean.class); private SecurityManager securityManager; private Map<String, Filter> filters; private Map<String, String> filterChainDefinitionMap; //urlPathExpression_to_comma-delimited-filter-chain-definition private String loginUrl; private String successUrl; private String unauthorizedUrl; private AbstractShiroFilter instance;
可以看到ShiroFilterFactoryBean的属性和我们shiroFilter bean的配置文件属性一一对应,spring在初始化shiroFilter的时候会把配置文件中的值通过setter方法set到实例中。
ShiroFilterFactoryBean实现了FactoryBean,FactoryBean是一个特殊Bean,当需要得到shiroFilter这个bean时,会调用getObject()来获取实例。
public Object getObject() throws Exception { if (instance == null) { instance = createInstance(); }
return instance; }
到这里我们应该知道shiroFilter这个bean就是AbstractShiroFilter的一个实例。我们来看看是如何创建这个shiroFilter实例的:
protected AbstractShiroFilter createInstance() throws Exception { log.debug("Creating Shiro Filter instance."); SecurityManager securityManager = getSecurityManager();
//配置文件必须配置securityManager if (securityManager == null) { String msg = "SecurityManager property must be set."; throw new BeanInitializationException(msg); }
//securityManager必须是WebSecurityManager的实例,也就是DefaultWebSecurityManager了 if (!(securityManager instanceof WebSecurityManager)) { String msg = "The security manager does not implement the WebSecurityManager interface."; throw new BeanInitializationException(msg); }
//创建FilterChainManager,用来管理Filter和FilterChain FilterChainManager manager = createFilterChainManager();
//创建FilterChainResolver,用来配合FIlterChainManager工作 PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver(); chainResolver.setFilterChainManager(manager);
//通过两个组件:securityManager chainResolver来实例化shiroFilter return new SpringShiroFilter((WebSecurityManager) securityManager, chainResolver); }
从上面的源码我们可以看到,shiroFilter = new SpringShiroFilter(securityManager, chainResolver), securityManager我们在spring的配置文件已经配置好了,它是shiro的“心脏”,类似于SpringMVC中的DispatcherServlet,
它是一个顶级组件,管理验证、授权、会话管理、加密等组件,所有和shiro交互都经过securityManager,相当于一个大管家。FilterChainManager从名字可以猜测到应该是用来管理Filter和FilterChain的,我们看看它是如何创建的:
protected FilterChainManager createFilterChainManager() {
//创建manager实例,实例化时会把shiro缺省的Filter(如 authc, anon, ssl, user等)缓存到DefaultFilterChainManager中的map属性中 DefaultFilterChainManager manager = new DefaultFilterChainManager();
//获取缺省的Filter Map<String, Filter> defaultFilters = manager.getFilters();
//把loginUrl, successUrl, unanthorizedUrl三个属性设置到相应的缺省Filter中 for (Filter filter : defaultFilters.values()) { applyGlobalPropertiesIfNecessary(filter); }
//这个filters是我们在配置文件中<property name="filters"/>节点中配置的自定义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); }
//这里可以看到,DefaultFilterChainManager的属性map中包含了缺省的Filter以及用户定义Filter,统一由manager管理 manager.addFilter(name, filter, false); } }
//这个map是用来缓存配置文件的<filterChainDefinitions>节点的数据的,如 /login = anon, /user/** = user 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();
//创建FilterChain,下面分析下这里的源码 manager.createChain(url, chainDefinition); } } return manager; }
可以看到是用DefaultFilterChainManager来创建manager实例的,我们先来看看DefaultFilterChainManager的属性,我们可以看到,DefaultFilterChainManager有两个重要的map,一个是filters,
就是我们上面分析的用来缓存缺省filter以及自定义filter的,另外的一个map filterChains是用来缓存NamedFilterList的,那么NamedFilterList是什么呢,通过源码可以看到它其实就是相当于一个List<Filter>!
我们在配置<filterChainDefinitions>节点时 url=filter是一对(一)多的,例如 /login=user, roles[admin], perms[user:edit],那么很明显这个filterChinas是用来缓存<filterChainDefinitions>节点的数据的。
public class DefaultFilterChainManager implements FilterChainManager { private static transient final Logger log = LoggerFactory.getLogger(DefaultFilterChainManager.class); private FilterConfig filterConfig;
//上面分析过了,主要用来缓存缺省的filter和配置文件中<property name="filters">节点的filter private Map<String, Filter> filters; //pool of filters available for creating chains
//这里我们猜测应该是用来缓存FilterChain的,也就是上面分析到的 manager.createChain(url, chainDefinition) private Map<String, NamedFilterList> filterChains; //key: chain name, value: chain
通过源码我们可以看到确实filterChains是用来缓存<filterChainDefinitions>节点数据的,在缓存过程中只是多了对 anon,user[edit:1]这些字符串进行解析,大家具体可以看看源码。
到这里我们manager实例就基本分析完毕了,主要还是在于两个map,这个两个map分别用来缓存<bean id="shiroFilter" >节点下的数据,还有缺省的Filter,如anno,ssl,user等缺省Filter。
那么我们回到创建shiroFilter实例的源码
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); }
//上面已经分析过manager的创建过程
FilterChainManager manager = createFilterChainManager();
//主要用来解析request的请求路径的,默认的ANT风格的路径,下面来分析这个类 PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver(); chainResolver.setFilterChainManager(manager); return new SpringShiroFilter((WebSecurityManager) securityManager, chainResolver); }
PathMatchingFilterChainResolver
PathMatchingFilterChainResolver有两个属性,FilterChainManager和PatternMatcher(AntPathMatcher),前者我们分析过了,后者是一个路径匹配器,是用来匹配请求路径的。
public class PathMatchingFilterChainResolver implements FilterChainResolver { private static transient final Logger log = LoggerFactory.getLogger(PathMatchingFilterChainResolver.class); private FilterChainManager filterChainManager; private PatternMatcher pathMatcher; public PathMatchingFilterChainResolver() { this.pathMatcher = new AntPathMatcher(); this.filterChainManager = new DefaultFilterChainManager(); } public PathMatchingFilterChainResolver(FilterConfig filterConfig) { this.pathMatcher = new AntPathMatcher(); this.filterChainManager = new DefaultFilterChainManager(filterConfig); } public PatternMatcher getPathMatcher() { return pathMatcher; } public void setPathMatcher(PatternMatcher pathMatcher) { this.pathMatcher = pathMatcher; } public FilterChainManager getFilterChainManager() { return filterChainManager; } @SuppressWarnings({"UnusedDeclaration"}) public void setFilterChainManager(FilterChainManager filterChainManager) { this.filterChainManager = filterChainManager; }
//主要的方法,originalChain为web.xml配置的Filter public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) { FilterChainManager filterChainManager = getFilterChainManager();
//manager 的 filterChains is empty ? 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
// filterchains.keySet() 也就是 url[] 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..."); }
//如果当前的请求url路径能匹配上,就返回一个代理FilterChain,这个代理FilterChain主要包含了originalChain(FilterChain)以及List<Filter>两个属性,后者为shiro配置的Filter return filterChainManager.proxy(originalChain, pathPattern); } } return null; } protected boolean pathMatches(String pattern, String path) { PatternMatcher pathMatcher = getPathMatcher(); return pathMatcher.matches(pattern, path); } protected String getPathWithinApplication(ServletRequest request) { return WebUtils.getPathWithinApplication(WebUtils.toHttp(request)); } }
FilterChainResolver只定义了一个方法 getChain(), PathMatchingFilterChainResolver实现了这个方法,当接收到请求时,PatternMatcher结合DefaultFilterChainManager的两个map的数据来匹配请求路径,没有匹配到路径的时候返回null,
匹配成功后返回一个ProxiedFilterChain实例,ProxiedFilterChain实现FilterChain接口,里面维护了一个FilterChain以及一个LIst<Filter>,FilterChain也就是参数originalChain,它是web.xml配置的Filter形成的过滤链,List<Filter>是请求路径配到的Shiro配置的Filter列表。
从源码可以看出如果请求路径能匹配到多个shiro的url时,只会匹配到第一url,因为filters和filterChains都是有序的LinkedHashMap类型。
到此 shiroFilter = new SpringShiroFilter(securityManager, chainResolver)也就创建完毕了。
现在分析下shiroFilter是如何工作的,shiroFilter也是一个Filter,Filter的工作入口点是doFilter(...)方法,我们先来看看类继承关系图:
OncePerRequestFilter改写了doFilter(...)方法:
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(...),这是个模板方法,在AbstractShiroFilter实现了
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 {
//有请求的话更新一下会话的LastAccessTime updateSessionLastAccessTime(request, response);
//重点分析 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); }
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; }
现在看来思路应该很清晰了。之前代码说到当请求没有匹配到shiro的Filter时返回null, 从上面代码我们可以看到当返回null时就执行originalChain也就是web.xml定义的FilterChain,如果匹配到了就返回一个代理FilterChain(ProxiedFilterChain)。
现在回到配置文件总结一下:
<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"> <!-- 自定义Filter --> <util:map> <entry key="authc" value-ref="formAuthenticationFilter" /> </util:map> </property> <property name="filterChainDefinitions"> <value> /resources/** = anon /login = ssl,authc /** = user </value> </property> </bean>
配置文件中的三个节点loginUrl, successUrl, unauthorizedUrl主要是用来设置Shiro相应的缺省Filter(下面会附上缺省Filter列表),filters节点是配置我们自定义的Filter,缺省Filter以及自定义Filter都会缓存在DefaultFilterChainManager的
Map<String, Filter> filters属性中。<filterChainDefinitions>节点配置的是url和Filter的关系,一个url可以配置多个Filter,形成FilterChain,该节点的数据都会缓存在DefaultFilterChainManager的 Map<String, NamedFilterList> filterChains
属性中,这两个map也是DefaultFilterChainManager的两个主要属性。
PathMatchingFilterChainResolver的作用是解析请求路径,返回FilterChain。它有两个重要属性:FilterChainManager和PatternMatcher,前者是上述说的DefaultFilterChainManager的实例,包含了两个map,缓存了Filter和FilterChain数据,
PatternMatcher是用来匹配请求路径的,采用的AntPathMatcher,匹配ANT风格路径。当请求路径路径匹配失败时,返回NULL,返回NULL时并非是不过滤请求了,而是用Web.xml配置的FilterChain来doFilter(...),如果匹配路径成功了,那么将
返回一个ProxiedFilterChain,这个类实现了FilterChain接口,它维护了两个重要属性,一个是web.xml配置的原始过滤链FilterChain,另一个是请求路径匹配成功的Shiro过滤器列表List<Filter>。这样这个请求就会调用相应的doFilter(...)方法
进行过滤。
至此,shiroFilter也就创建完成, shiroFilter = new SpringShiroFilter(securityManager, filterChainResolver)。
附上shiro缺省Filter
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); private final Class<? extends Filter> filterClass; private DefaultFilter(Class<? extends Filter> filterClass) { this.filterClass = filterClass; } public Filter newInstance() { return (Filter) ClassUtils.newInstance(this.filterClass); } public Class<? extends Filter> getFilterClass() { return this.filterClass; } public static Map<String, Filter> createInstanceMap(FilterConfig config) { Map<String, Filter> filters = new LinkedHashMap<String, Filter>(values().length); for (DefaultFilter defaultFilter : values()) { Filter filter = defaultFilter.newInstance(); if (config != null) { try { filter.init(config); } catch (ServletException e) { String msg = "Unable to correctly init default filter instance of type " + filter.getClass().getName(); throw new IllegalStateException(msg, e); } } filters.put(defaultFilter.name(), filter); } return filters; } }
至今转行做Java已经半年有多,水平很差,如果有错误希望各位不吝赐教。
年龄较大,发现很多不常用的知识学习后稍纵即忘,写博客以记录之,并且与君共勉。