Shiro源码研究之ShiroFilterFactoryBean

10 篇文章 1 订阅
5 篇文章 0 订阅

公司终于决心搞一套完备的权限校验系统,借此机会本人举荐了Shiro,关于使用手册,开涛大神的文章已经足够详细了,所以我就不再班门弄斧。故转而研究下Shiro的内部实现;传统IT行业需求奇葩,现在了解得深入一些,以后的应对也能相对从容很多。

1. 相关配置

<!-- web.xml -->
<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>

<!-- spring-shiro.xml -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
	<!-- 必配 -->
    <property name="securityManager" ref="securityManager"/>
    <!-- 登录界面设置 -->
	<property name="loginUrl" value="/login.html" /> 
    <property name="filters">
        <util:map>
            <entry key="authc" value-ref="upmsFormAuthenticationFilter"/>
		 	<entry key="user" value-ref="MyUserAuthenticationFilter"/>               	                
            <entry key="kanqModuleAuthc" value-ref="upmsModuleAuthenticationFilter"/>
        </util:map>
    </property>
    <property name="filterChainDefinitions">
		    <!--
				关于这里, 
					1. 如果你定义是多个同名的key, 那么Shiro会将其合并.
					2. 如果当前请求匹配多个key, Shiro只会取第一个匹配的.
					3. 以上是默认行为, 如果有特殊需求可以覆写。
			-->
            /login.jsp = authc
            /logout = logout
            /unauthorized.jsp = anon
			/main**           = authc
            /admin/list**     = authc,perms[admin:manage]
            /user/info-anon** = anon
            /user/info**      = authc           
            /** = anon           	
        </value>
    </property>
</bean>

<!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
	<property name="realms">
		<list><ref bean="upmsRealm"/></list>
	</property>
	<property name="sessionManager" ref="sessionManager"/>
	<property name="rememberMeManager" ref="rememberMeManager"/>
</bean>

2. 继承链

再来看看继承链
ShiroFilterFactoryBean继承链

鼎鼎大名的FactoryBean<T>BeanPostProcessor接口赫然在目。作用就不提了,直接看实现。

2.1 BeanPostProcessor接口的实现

@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
	if (bean instanceof Filter) {
		log.debug("Found filter chain candidate filter '{}'", beanName);
		Filter filter = (Filter) bean;
		// 应用全局配置
		applyGlobalPropertiesIfNecessary(filter);
		// 将在Spring中注册(而不是在ShiroFilterFactoryBean中配置的)的Filter并入.
		// 这里面就牵扯出一个有趣的问题: FactoryBean<T>接口的getObject方法和BeanPostProcessor接口的postProcessBeforeInitialization的执行先后顺序?
		// 为了保证不遗漏Filter, 我们可以猜测后者必须优先于前者。
		getFilters().put(beanName, filter);
	} else {
		log.trace("Ignoring non-Filter bean '{}'", beanName);
	}
	return bean;
}

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
	return bean;
}
2.2 FactoryBean<T>接口的实现
@Override`
public Object getObject() throws Exception {
	if (instance == null) {
		instance = createInstance();
	}
	return instance;
}

@Override
public Class getObjectType() {
	return SpringShiroFilter.class;
}

@Override
public boolean isSingleton() {
	return true;
}

ShiroFilterFactoryBean.SpringShiroFilter类就是作为FactoryBean<T>接口最终要返回的实例;关于执行逻辑我们放在下一篇博客,这里我们先看看关键的createInstance()方法

2.2.1 ShiroFilterFactoryBean.createInstance方法
// ShiroFilterFactoryBean.createInstance
protected AbstractShiroFilter createInstance() throws Exception {

	log.debug("Creating Shiro Filter instance.");

	// 确保用户配置了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();


	//Expose the constructed FilterChainManager by first wrapping it in a
	// FilterChainResolver implementation. The AbstractShiroFilter implementations
	// do not know about FilterChainManagers - only resolvers:
	// 将FilterChain的管理工作(FilterChainManager)从FilterChainResolver中剥离出去, FilterChainResolver只负责检索, 而将管理工作委托给了FilterChainManager.
	// 这里是写死为PathMatchingFilterChainResolver
	PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();
	chainResolver.setFilterChainManager(manager);

	//Now create a concrete ShiroFilter instance and apply the acquired SecurityManager and built
	//FilterChainResolver.  It doesn't matter that the instance is an anonymous inner class
	//here - we're just using it because it is a concrete AbstractShiroFilter instance that accepts
	//injection of the SecurityManager and FilterChainResolver:
	// 构造SpringShiroFilter实例,由Spring管理
	return new SpringShiroFilter((WebSecurityManager) securityManager, chainResolver);
}
2.2.2 ShiroFilterFactoryBean.createFilterChainManager方法
// ShiroFilterFactoryBean.createFilterChainManager
protected FilterChainManager createFilterChainManager() {

	// 这个构造函数中会将shiro默认的Filter添加到FilterChainManager中.
	DefaultFilterChainManager manager = new DefaultFilterChainManager();
	// 为默认加载的Filter,应用上全局配置属性
	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:
	// 然后再将用户配置的Filter并入; 所以如果用户配置了与上面同名的Filter, 则会进行覆盖操作.
	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:
			// spring会处理初始化问题, 所以shiro就不负责初始化了
			manager.addFilter(name, filter, false);
		}
	}

	//build up the chains:
	// 这里将处理用户配置的ShiroFilterFactoryBean.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();
			// 接下来进行讲解	
			manager.createChain(url, chainDefinition);
		}
	}

	return manager;
}
2.2.3 DefaultFilterChainManager构造函数
// DefaultFilterChainManager构造函数
public DefaultFilterChainManager() {
	this.filters = new LinkedHashMap<String, Filter>();
	this.filterChains = new LinkedHashMap<String, NamedFilterList>(); 
	addDefaultFilters(false);
}

protected void addDefaultFilters(boolean init) {
	// DefaultFilter为shiro内置的枚举类型, 其中定义了shiro启动时默认会加载的Filter
	for (DefaultFilter defaultFilter : DefaultFilter.values()) {
		addFilter(defaultFilter.name(), defaultFilter.newInstance(), init, false);
	}
}
2.2.4 DefaultFilterChainManager.createChain方法
  1. 开始讲解这个方法之前,我们先看看shiro是如何处理上面spring-shiro.xml中用户配置的ShiroFilterFactoryBean.filterChainDefinitions属性的

    //---------- ShiroFilterFactoryBean.setFilterChainDefinitions方法
    public void setFilterChainDefinitions(String definitions) {
    	// 使用Ini类来解析用户配置的信息
    	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);
    	}
    	// 将解析出来的结果赋值给ShiroFilterFactoryBean的filterChainDefinitionMap字段
    	setFilterChainDefinitionMap(section);
    }
    
  2. 然后我们来看DefaultFilterChainManager.createChain的实现
    这个方法执行完之后,用户配置的url权限校验(即ShiroFilterFactoryBeanfilterChainDefinitions参数)就算是解析到位了(存放到了DefaultFilterChainManagerfilterChains参数中)。

    //---------- DefaultFilterChainManager.createChain
    public void createChain(String chainName, String chainDefinition) {
    	// 以上面配置的filterChainDefinitions参数举例:	
    	// 参数chainName形如 /admin/list**
    	// 参数chainDefinition形如 authc,perms[admin:manage]
    	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.");
    	}
    
    	//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]" }
    	//
    	// 我故意保留了上面这些注释.
    	//  以上我们就可以看出我们所配置的ShiroFilterFactoryBean的filterChainDefinitions里的 每一行 会在这里被完整解析
    	String[] filterTokens = splitChainDefinition(chainDefinition);
    
    	//each token is specific to each filter.
    	//strip the name and extract any filter-specific config between brackets [ ]
    	for (String token : filterTokens) {
    		/* toNameConfigPair的解析结果参见下面这个,摘选自官方
    		Input 				Result 
    		
    		foo 				returned[0] == foo
    							returned[1] == null 
    							
    		foo[bar, baz] 		returned[0] == foo
    							returned[1] == bar, baz 
    		*/
    		String[] nameConfigPair = toNameConfigPair(token);
    
    		//now we have the filter name, path and (possibly null) path-specific config.  Let's apply them:
    		// 下面将进行详解
    		addToChain(chainName, nameConfigPair[0], nameConfigPair[1]);
    	}
    }
    
2.2.5 DefaultFilterChainManager.addToChain方法
// DefaultFilterChainManager.addToChain方法
public void addToChain(String chainName, String filterName, String chainSpecificFilterConfig) {
	if (!StringUtils.hasText(chainName)) {
		throw new IllegalArgumentException("chainName cannot be null or empty.");
	}
	
	// 如果用户没有配置该filter, 则直接抛出的异常
	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).");
	}

	// 保存用户配置的url与filter之间的映射关系
	applyChainConfig(chainName, filter, chainSpecificFilterConfig);

	// chainName为配置的url路径
	// 这里会以用户配置的url路径来创建一个SimpleNamedFilterList示例;  并添加到DefaultFilterChainManager内部的Map<String, NamedFilterList>类型的类级字段filterChains中(以用户配置的url路径为key——即filterChainDefinitions参数里每一行等号左边的部分)
	NamedFilterList chain = ensureChain(chainName);
	// 对于同一个url, 用户可能配置多个Filter
	chain.add(filter);
}

3. 细节

通过观察以上接口实现,发现一个有趣的问题: FactoryBean<T>接口的getObject方法和BeanPostProcessor接口的postProcessBeforeInitialization的执行先后顺序?

为了保证不遗漏Filter,我们可以猜测后者必须优先于前者。

4. 参考

  1. 《跟我学Shiro》——开涛
  • 3
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值