Shiro源码分析(8) - 过滤器(ShiroFilterFactoryBean)

原创
2018/02/08 18:30
阅读数 2.7K

配置说明

ShiroFilterFactoryBean是Spring为Shiro框架提供的一个整合类,在项目使用过程中通常会使用如下的配置方式来处理。

<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">  
    <!-- 安全管理器-->
    <property name="securityManager" ref="securityManager"/>  
    <!-- 登录地址-->
    <property name="loginUrl" value="/login.jsp"/>  
    <!-- 未授权跳转地址-->
    <property name="unauthorizedUrl" value="/unauthorized.jsp"/>  
    <!--配置自定义的过滤器-->
    <property name="filters">  
        <util:map>  
            <entry key="authc" value-ref="formAuthenticationFilter"/>  
        </util:map>  
    </property>  
    <!--配置请求路径过滤规则-->
    <property name="filterChainDefinitions">  
        <value>  
            /index.jsp = anon  
            /login.jsp = authc  
            /logout = logout  
            /** = user  
        </value>  
    </property>  
</bean>   

在上面的配置中需要解释一下filterChainDefinitions这个属性的配置。例如/logout = logout表示请求地址为/logout的请求需要被logout过滤。下面重点是在分析对filterChainDefinitions配置解析处理。

ShiroFilter

ShiroFilterFactoryBean作为一个工厂Bean,将ShiroFilter实例对象注入到Spring容器中去,这样Shiro基于url的方式进行请求过滤处理。

在ShiroFilterFactoryBean中主要需要分析的方法是createInstance(),该方法创建了ShiroFilter对象实例,将SpringShiroFilter注入到容器中去。

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);
	}
        // WEB环境中,必须是WebSecurityManager类型
	if (!(securityManager instanceof WebSecurityManager)) {
		String msg = "The security manager does not implement the WebSecurityManager interface.";
		throw new BeanInitializationException(msg);
	}
    
        //管理着Shiro提供的默认过滤器信息
	FilterChainManager manager = createFilterChainManager();

	//获取路径匹配过滤器链的处理对象
	PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();
	chainResolver.setFilterChainManager(manager);

	// 创建SpringShiroFilter对象
	return new SpringShiroFilter((WebSecurityManager) securityManager, chainResolver);
}

我们分三个步骤来分析上面的方法,分别是:

  • 过滤器管理器(FilterChainManager)是如何管理过滤器
  • PathMatchingFilterChainResolver 在SpringShiroFilter中的作用
  • SpringShiroFilter中过滤器实现

过滤器链管理器(FilterChainManager)

FilterChainManager有一个实现类DefaultFilterChainManager负责管理存在的Filter信息。在DefaultFilterChainManager实例化的时候会加载Shiro提供的一些默认的Filter实例对象。

public DefaultFilterChainManager() {
	this.filters = new LinkedHashMap<String, Filter>();
	this.filterChains = new LinkedHashMap<String, NamedFilterList>();
        // 添加默认的过滤器,false参数表示是否需要初始化FilterConfig信息
	addDefaultFilters(false);
}

public DefaultFilterChainManager(FilterConfig filterConfig) {
	this.filters = new LinkedHashMap<String, Filter>();
	this.filterChains = new LinkedHashMap<String, NamedFilterList>();
	setFilterConfig(filterConfig);
        // 添加默认的过滤器,需要初始化FilterConfig信息
	addDefaultFilters(true);
}

protected void addDefaultFilters(boolean init) {
        // 默认的过滤器信息存放在DefaultFilter这个枚举类中
	for (DefaultFilter defaultFilter : DefaultFilter.values()) {
                // 将默认的过滤器实例添加到filters属性中去
		addFilter(defaultFilter.name(), defaultFilter.newInstance(), init, false);
	}
}

在Shiro中为我们提供了如下默认的过滤器,配置在DefaultFilter枚举类中。

// 通用过滤器,任何请求允许访问
anon(AnonymousFilter.class),
// 表单认证过滤器
authc(FormAuthenticationFilter.class),
// 基于Http请求的认证过滤器
authcBasic(BasicHttpAuthenticationFilter.class),
// 登出过滤器
logout(LogoutFilter.class),
// 不创建Session过滤器
noSessionCreation(NoSessionCreationFilter.class),
// 权限认证过滤器
perms(PermissionsAuthorizationFilter.class),
// 端口过滤器
port(PortFilter.class),
// 请求处理为权限的一种过滤器
rest(HttpMethodPermissionFilter.class),
// 角色过滤器
roles(RolesAuthorizationFilter.class),
// SSL过滤器
ssl(SslFilter.class),
// 用户过滤器,检测用户是否登录
user(UserFilter.class);

现在我们还是回到createFilterChainManager()方法,这个方法创建了FilterChainManager对象,也就是上面分析的DefaultFilterChainManager构造方法。在创建了DefaultFilterChainManager后,需要对配置的过滤器和请求URL进行处理。下面我们具体分析过滤器是如何被管理的。

protected FilterChainManager createFilterChainManager() {
        // 创建实例对象(上面提到的默认过滤器已经添加进来了)
	DefaultFilterChainManager manager = new DefaultFilterChainManager();
        //获取默认的过滤器
	Map<String, Filter> defaultFilters = manager.getFilters();
	// 为默认的过滤器设置登录URL、登录成功跳转URL和未授权跳转URL这些熟悉信息
	for (Filter filter : defaultFilters.values()) {
		applyGlobalPropertiesIfNecessary(filter);
	}

	//获取配置指定的过滤器(在xml配置示例中配置的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);
			}
                        // 将配置的过滤器也添加到过滤器链管理器中去
			manager.addFilter(name, filter, false);
		}
	}

	//获取配置的filterChainDefinitions属性信息:
        //          /index.jsp = anon  
        //          /login.jsp = authc  
        //          /logout = logout  
        //          /** = 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();
                        // url表示请求的地址
                        // chainDefinition表示该地址需要使用的过滤器链信息
			manager.createChain(url, chainDefinition);
		}
	}

	return manager;
}

为了搞明白配置的filterChainDefinitions属性是如何使用的,我们继续分析刚才的manager.createChain(url, chainDefinition)调用。这个方法将配置的值解析成键值对的过滤器链存放在filterChains属性中去。具体方法如下:

public void createChain(String chainName, String chainDefinition) {
        // 检测配置key-value必须存在
	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 + "]");
	}

        //这里需要解析一些过滤器的配置
        // 因为配置的值可能是 "authc, roles[admin,user], perms[file:edit]"
        // 解析后得到的对象值为{ "authc", "roles[admin,user]", "perms[file:edit]" }
	String[] filterTokens = splitChainDefinition(chainDefinition);

        // 继续解析过滤器,例如:  foo[bar, baz]
        // 解析后得到:{ "foo", "bar, baz" }
	for (String token : filterTokens) {
		String[] nameConfigPair = toNameConfigPair(token);
                // 将得到的过滤器信息添加到过滤器链中
		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 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).");
	}
        
        // 为过滤器设置一些特定的配置信息,由PathConfigProcessor接口来完成这个功能
	applyChainConfig(chainName, filter, chainSpecificFilterConfig);
        // 添加到过滤器链中
        // 这里处理的原理为:每个chainName(也就是配置中的请求URL)都关联一个NamedFilterList chain实例对象,并存放在filterChains Map中管理。
	NamedFilterList chain = ensureChain(chainName);
	chain.add(filter);
}

在DefaultFilterChainManager类中还有一个方法需要说明,那就是FilterChain proxy(FilterChain original, String chainName)方法。当进行请求过滤时,这个方法会代理处理过滤器链。

public FilterChain proxy(FilterChain original, String chainName) {
        // 根据chainName获取到相应的NamedFilterList对象
        // 这个就是上面分析中看到的,将过滤器信息存放在NamedFilterList对象中
	NamedFilterList configured = getChain(chainName);
        // 没有匹配到就抛异常
	if (configured == null) {
		String msg = "There is no configured chain under the name/key [" + chainName + "].";
		throw new IllegalArgumentException(msg);
	}
        // 对原来的FilterChain做代理
	return configured.proxy(original);
}

代理处理在ProxiedFilterChain#doFilter(ServletRequest request, ServletResponse response) 方法中。

public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
        // 如果filters为空,或代理已经处理完,则处理原先的FilterChain
	if (this.filters == null || this.filters.size() == this.index) {
		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 + "]");
		}
                // 逐一调用filters中的过滤器
                // 注意,这个传入的FilterChain为ProxiedFilterChain本身。
		this.filters.get(this.index++).doFilter(request, response, this);
	}
}

SpringShiroFilter分析

在Shiro框架中提供了对javax.servlet.Filter接口的实现,其实现的抽象类层次为OncePerRequestFilter、AdviceFilter、PathMatchingFilter、AccessControlFilter、AuthenticationFilter、AuthenticatingFilter。对于Shiro中的Filter实现,我们将逐一进行分析。

OncePerRequestFilter

在OncePerRequestFilter类中处理了两个功能:第一,标记那些过滤器执行过,确保不重复执行;第二,使用enable来表示是否需要执行当前的过滤器,enable默认为true,isEnabled(request, response) 方法作为过滤器是否执行的判断依据。

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 
        // enable表示是否开启当前执行器,默认enable=true
        if (!isEnabled(request, response) ) {
		log.debug("Filter '{}' is not enabled for the current request.  Proceeding without invoking this filter.", getName());
		filterChain.doFilter(request, response);
	} else {
		// 执行当前过滤器
		log.trace("Filter '{}' not yet executed.  Executing now.", getName());
                // 标记已经执行
		request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
		try {
                        // 抽象方法,子类实现过滤器执行
			doFilterInternal(request, response, filterChain);
		} finally {
                        // 
			request.removeAttribute(alreadyFilteredAttributeName);
		}
	}
}

AdviceFilter

AdviceFilter过滤器对OncePerRequestFilter进行了进一步的抽象,对过滤器的执行添加了前置处理、后置处理和完成后的处理。分别提供下面方法进行:

// 前置处理方法,返回true表示过滤器可以继续执行下去
boolean preHandle(ServletRequest request, ServletResponse response)

// 后置处理方法
void postHandle(ServletRequest request, ServletResponse response)    

// 完成后的处理方法,这里对异常情况做处理
void afterCompletion(ServletRequest request, ServletResponse response, Exception exception)

下面完整的分析下从OncePerRequestFilter实现的doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)方法。

public void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
		throws ServletException, IOException {

	Exception exception = null;

	try {
                // 过滤器前置处理方法(钩子方法),如果返回true表示继续执行过滤器链
		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 {
                //在这里调用了afterCompletion(request, response, exception)方法
                //对异常做处理
		cleanup(request, response, exception);
	}
}

PathMatchingFilter

PathMatchingFilter在AdviceFilter的基础上对前置处理方法preHandle(ServletRequest request, ServletResponse response)进行了重写,PathMatchingFilter类对请求的路径做过滤器匹配,判断是否可以继续执行过滤器链。

在分析preHandle(ServletRequest request, ServletResponse response)方法之前,我们先看看PathConfigProcessor接口,PathMatchingFilter实现了PathConfigProcessor接口,它用来标记配置的过滤器路径信息。

我们在上面分析DefaultFilterChainManager#addToChain(String chainName, String filterName, String chainSpecificFilterConfig)方法的时候说过。在添加过滤器到DefaultFilterChainManager中时,如果过滤器实现了PathConfigProcessor接口,那么会调用PathConfigProcessor#processPathConfig(String path, String config)方法,将键值对添加到PathConfigProcessor的appliedPaths 属性中去。例如配置中存在:/foo/.html = roles[admin,user]。那么添加到appliedPaths 中的就会是/foo/.html = admin,user,这时的过滤器实例就是RolesAuthorizationFilter。

public Filter processPathConfig(String path, String config) {
	String[] values = null;
	if (config != null) {
		values = split(config);
	}
        // 上面的例子中
        // path = /foo/*.html
        // values = [admin,user]
	this.appliedPaths.put(path, values);
	return this;
}

我们了解了appliedPaths属性是如何来的之后,我们开始分析preHandle(ServletRequest request, ServletResponse response)方法。

protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        // 过滤器没有特殊的配置,允许通过
	if (this.appliedPaths == null || this.appliedPaths.isEmpty()) {
		if (log.isTraceEnabled()) {
			log.trace("appliedPaths property is null or empty.  This Filter will passthrough immediately.");
		}
		return true;
	}

	for (String path : this.appliedPaths.keySet()) {
		// 判断请求路径是否和配置路径匹配
		if (pathsMatch(path, request)) {
			log.trace("Current requestURI matches pattern '{}'.  Determining filter chain execution...", path);
                        // 获取配置的具体信息
			Object config = this.appliedPaths.get(path);
                        // 进一步判断过滤器是否可以执行
                        // 在这里委派给子类去实现onPreHandle(request, response, pathConfig)方法来决定是否需要继续执行过滤器链
			return isFilterChainContinued(request, response, path, config);
		}
	}

	// 允许通过
	return true;
}

从上面的分析来看,在PathMatchingFilter类中对请求路径进行了匹配过滤,通过路径来判断当前的请求是否需要经过当前过滤器来处理。另外,提供了onPreHandle(request, response, pathConfig)这个钩子方法来判断是否执行过滤器。

AccessControlFilter

AccessControlFilter类是一个访问控制过滤器,在AccessControlFilter中提供了loginUrl属性来表示登录的URL地址。提供了两个抽象方法:

  • isAccessAllowed(request, response, mappedValue),表示方式是否被允许
  • onAccessDenied(request, response, mappedValue),当isAccessAllowed方法返回false时,使用这个方法来代替处理是否继续执行过滤器链。

下面是被重写的onPreHandle方法。

    public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
        return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);
    }
展开阅读全文
打赏
0
1 收藏
分享
加载中
更多评论
打赏
0 评论
1 收藏
0
分享
返回顶部
顶部