安全认证框架Shiro (二)- shiro过滤器工作原理
第一:前言
由于工作原因,写上篇文章安全认证框架Shiro (一)- ini配置文件过了好久,这里补上Shiro的后续学习经历。
第二:ShiroFilterFactoryBean入口
ShiroFilterFactoryBean实现FactoryBean,说明它是ShiroFilter的工厂类。它是怎么初始化让Shiro能很好的工作的呢,该类的入口方法是createInstance(),该方法实现了几个功能
1.创建了一个过滤器管理类FilterChainManager,该类主要管理shiro里的过滤器,里面有2个重要的属性
1.1 filters:管理全部过滤器,包括默认的关于身份验证和权限验证的过滤器,这些过滤器分为两组,一组是认证过滤器,有anon,authcBasic,auchc,user,一组是授权过滤器,有perms,roles,ssl,rest,port。同时也包含在xml里filters配置的自定义过滤器。在其它地方使用时都是从过滤器管理类里filters里拿的。且过滤器是单例的,整个Shiro框架只维护每种类型过滤器的单例。
1.2 filterChains:过滤链。它是我们重点关注的东西,是一个Map对象,其中key就是我们请求的url,value是一个NamedFilterList对象,里面存放的是与url对应的一系列过滤器。这后面会详细讲解。
2.将过滤器管理类设置到PathMatchingFilterChainResolver类里,该类负责路径和过滤器链的解析与匹配。根据url找到过滤器链。
我们以如下的xml配置为例讲解,如下:
<!-- Shiro的Web过滤器 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<property name="loginUrl" value="/login.jsp"/>
<property name="filters">
<util:map>
<entry key="authc" value-ref="formAuthenticationFilter"/>
<entry key="logout" value-ref="logoutFilter" />
</util:map>
</property>
<property name="filterChainDefinitionMap" ref="filterChainDefinitionMap" />
</bean>
<bean id="filterChainDefinitionMap"
class="com.haedrig.shiro.spring.ChainDefinitionSectionMetaSource">
<!-- 默认的连接配置 -->
<property name="filterChainDefinitions">
<value>
/login.jsp = anon
/login = authc
/logout = logout
/authenticated = authc
/views/**=anon
/** = authc, perms
</value>
</property>
</bean>
createInstance()源码如下:
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);
}
//创建默认的过滤器管理器
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:
PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();
//将过滤器管理器设置到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:
return new SpringShiroFilter((WebSecurityManager) securityManager, chainResolver);
}
该方法创建过滤器管理器,同时设置到PathMatchingFilterChainResolver对象里,当有请求过来里,shiro会通过PathMatchingFilterChainResolver解析得到请求的url对应的过滤器链。
createFilterChainManager()方法:
protected FilterChainManager createFilterChainManager() {
DefaultFilterChainManager manager = new DefaultFilterChainManager();
//获得shiro默认的过滤器,同时将xml里配置的loginUrl,successUrl,unauthorizedUrl设置到不同过滤器里
Map<String, Filter> defaultFilters = manager.getFilters();
//apply global settings if necessary:
for (Filter filter : defaultFilters.values()) {
applyGlobalPropertiesIfNecessary(filter);
}
//将自定义过滤器添加到filters里让管理器管理,注意,filters是Map,所以key同名的会被覆盖。
//Apply the acquired and/or configured 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);
}
//'init' argument is false, since Spring-configured filters should be initialized
//in Spring (i.e. 'init-method=blah') or implement InitializingBean:
manager.addFilter(name, filter, false);
}
}
//build up the chains:
//获得xml里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();
//根据url创建url对应的过滤链**重点**重点**重点,每个配置过的url都对应一个过滤链
manager.createChain(url, chainDefinition);
}
}
return manager;
}
讲解:
1.applyGlobalPropertiesIfNecessary(filter);方法是将配置的loginUrl,successUrl,unauthorizedUrl设置到不同过器里。其中loginUrl赋值到所以继承自AccessControlFilter的过滤器里,successUrl赋值到所以继承自AuthenticationFilter的过滤器里,unauthorizedUrl赋值到所以继承自AuthorizationFilter的过滤器里。
当然其实过滤器可以自己设置loginUrl,successUrl,unauthorizedUrl,自定义赋值的也覆盖全局指定的。
2.读取filters配置的自定义过滤器,将它们纳入到过滤器管理器里。
3.最后读取filterChainDefinitions配置,根据配置设置每个url对应的过滤链,filterChains保存这些配置,它是一个Map集合,key就是url,value就是过滤器组成的NamedFilterList集合。其实当请求过来时,解析出请求路径,会从filterChains里找到url对应的过滤链,按过滤器的策略一个一个执行下去。
同时会调用PathMatchingFilter的processPathConfig()方法做些赋值操作。下面会专门讲将从PathMatchingFilter开始工作的过程。
4.一步一步分析下来过滤器管理器里的过滤链filterChains如下:
{
/login.jsp=org.apache.shiro.web.filter.mgt.SimpleNamedFilterList@6ed97422,
/login=org.apache.shiro.web.filter.mgt.SimpleNamedFilterList@592dbd8,
/logout=org.apache.shiro.web.filter.mgt.SimpleNamedFilterList@1141badb,
/authenticated=org.apache.shiro.web.filter.mgt.SimpleNamedFilterList@2d1b876e,
/views/**=org.apache.shiro.web.filter.mgt.SimpleNamedFilterList@31106774,
/**=org.apache.shiro.web.filter.mgt.SimpleNamedFilterList@14cd7d10,
/index.jsp=org.apache.shiro.web.filter.mgt.SimpleNamedFilterList@712384a,
/admin.jsp=org.apache.shiro.web.filter.mgt.SimpleNamedFilterList@7643ef31
}
可以看到每个url对应一个SimpleNamedFilterList对象,SimpleNamedFilterList是个List子类对象,保存的是过滤器集。
第三:请求到来解析过程
我们知道DelegatingFilterProxy过滤器的代理类来实现拦截的,任何请求都会先经过shiro先过滤,直到成功才会执行javaweb本身的过滤器。
一个请求过来时,先到达AbstractShiroFilter.executeChain()方法,去根据request解析出来的url找到对应的过滤链,然后执行过滤器链。
executeChain()方法如下:
protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain)
throws IOException, ServletException {
//得到过滤器链
FilterChain chain = getExecutionChain(request, response, origChain);
chain.doFilter(request, response);
}
进入getExecutionChain()方法:
protected FilterChain getExecutionChain(ServletRequest request, ServletResponse response, FilterChain origChain) {
FilterChain chain = origChain;
//resolver即是前面说的PathMatchingFilterChainResolver对象。里面保存有过滤器管理器实例
FilterChainResolver resolver = getFilterChainResolver();
if (resolver == null) {
log.debug("No FilterChainResolver configured. Returning original FilterChain.");
return origChain;
}
//进入PathMatchingFilterChainResolver对象里,根据解析出来的requestURI找到对应的过滤器链并返回
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;
}
最终返回的过滤器链是:
而backingList里数据如下:
我们们在xml里配置的是一样的。
/login = authc
再往下一步一步走,中间过程省略看图
直到FormAuthenticationFilter.doFilterInternal()方法
public void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
throws ServletException, IOException {
Exception exception = null;
try {
boolean continueChain = preHandle(request, response);
if (log.isTraceEnabled()) {
log.trace("Invoked preHandle method. Continuing chain?: [" + continueChain + "]");
}
if (continueChain) {
executeChain(request, response, chain);
}
postHandle(request, response);
if (log.isTraceEnabled()) {
log.trace("Successfully invoked postHandle method");
}
} catch (Exception e) {
exception = e;
} finally {
cleanup(request, response, exception);
}
}
这行代码boolean continueChain = preHandle(request, response);其实是真正直到的单个过滤器里了,要想知道这里面在干什么,需要往下继续看。
第四:过滤器执行原理
先从PathMatchingFilter类讲解开始,匹配路径的过滤器,因为所以的过滤器都会继承PathMatchingFilter类,它的作用是路径匹配。过滤器单独维护自己需要过滤的url。
整体看一下类的属性:
分别介绍属性详情:
appliedPaths:是个map类,存放需要过滤的URL的。
比如如下的配置:
会将这红框中的属性添加到appliedPaths集合里。
结果如下:
pathMatcher:匹配器,就是当请求过来时,匹配哪个url对应哪个过滤器的。
processPathConfig:解析xml里配置的url对应的过滤器,分别加到appliedPaths要应用的map集合里,由于每个过滤器都继承PathMatchingFilter,故每个过滤器都会经过这步操作。
比如如下图,刚启动web服务时过滤器管理器会解析xml里filterChainDefinitions配置的过滤链,在根据配置给每个url创建过滤器链时,会调用不同过滤器的processPathConfig方法让过滤器自己把url添加到appliedPaths集合里,因为过滤器是单例的,过滤器管理器的filters里也只维护过滤器的单个实例:
preHandle方法:请求过来时该方法匹配url路径是否是该过滤器要处理的。遍历appliedPaths里所有的url直到完全匹配成功或遍历完为止。如果匹配成功则表明该url请求是需要该过滤器处理。然后就会进入onPreHandle方法。
onPreHandle方法:该方法在路径匹配成功时决定对url是否需要身份验证。默认是返回true,意思是不需要验证的。但子类需要根据业务逻辑自己重写该方法。
看看对该方法的实现类有哪些。
其中重点看看AccessControlFilter和AnonymousFilter。一个是需要身份验证的,一个是匿名访问的。
AnonymousFilter就比较简单了,任何对象访问都一直返回true,表明任何用AnonymousFilter过滤的请求都不需要验证。因为它一直返回true。
AccessControlFilter是需要身份验证的过滤器。当请求在过滤器里匹配成功后。后续验证处理在这里。如图:
如果isAccessAllowed()验证成功则返回true,否则交由onAccessDenied()方法处理,最后将onAccessDenied()方法处理的结果返回。
isAccessAllowed()方法是决定了当前请求的subject是否允许访问。如果以前做过验证则返回true,否则返回false。
onAccessDenied()方法是在被拒绝访问时处理。AccessControlFilter类有很多子类重载了该方法。以FormAuthenticationFilter类的onAccessDenied()方法为例。
如果是登陆请求,则执行登陆操作,否则保存请求链接跳转到登陆请求界面。
executeLogin()方法就比较简单了。创建 token,获得Subject对象,然后执行login()最后到realm里查询数据库做比较,这里先不讲。
saveRequestAndRedirectToLogin()方法会执行WebUtils.saveRequest()将请求保存到session里
redirectToLogin()即是服务器跳转到登陆界面。
流程如下:
2017-11-16补
有必要再补充PathMatchingFilter里属性appliedPaths和过滤器链的关系:
如下图:
每个过滤器自己在appliedPaths集合维护自己需要处理的url集合,而url对应的过滤器链可能串连多个,它只要求对应的过滤器有自己的url即可,不管过滤器是否还要处理其它url.
第五:总结
入口在ShiroFilterFactoryBean,每个过滤器都会根据类型不同拥有loginUrl,successUrl,unauthorizedUrl中的一个或多个配置,过滤器管理器负责为配置的每个url创建过滤器链,对于没配置的url则对应到“/**”路径的过滤器链上。当请求过来时,ShiroFilterFactoryBean负责接收请求并让过滤器管理器通过一定的策略找到url对应的过滤器链执行过滤器链,这里有个特殊情况,登陆请求,如果是登陆请求且是POST方式提交的话,如果没登陆会去执行登陆操作。
未完这里,未完待续…..