公司终于决心搞一套完备的权限校验系统,借此机会本人举荐了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. 继承链
再来看看继承链
鼎鼎大名的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
方法
-
开始讲解这个方法之前,我们先看看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); }
-
然后我们来看
DefaultFilterChainManager.createChain
的实现
这个方法执行完之后,用户配置的url权限校验(即ShiroFilterFactoryBean
的filterChainDefinitions
参数)就算是解析到位了(存放到了DefaultFilterChainManager
的filterChains
参数中)。//---------- 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. 参考
- 《跟我学Shiro》——开涛