shiro集成spring初始化分析

记录下学习shiro的过程,原来学过一边,不过忘得这也差不多了,这次就不打算复习一遍了,而是从源码开始看。希望这次可以记住的时间长点。

这次的入口是DelegatingFilterProxy类,其在web.xml中以filter的配置存在,先看一下配置:

<filter>
	<filter-name>shiroFilter</filter-name>
	<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
		<!-- 设置true由servlet容器控制filter的生命周期 -->
	<init-param>
		<param-name>targetFilterLifecycle</param-name>
		<param-value>true</param-value>
	</init-param>
	<!-- 设置spring容器filter的bean id,如果不设置则找与filter-name一致的bean-->
	<init-param>
		<param-name>targetBeanName</param-name>
		<param-value>shiroFilter</param-value>
	</init-param>
</filter>
<filter-mapping>
	<filter-name>shiroFilter</filter-name>
	<url-pattern>/*</url-pattern>
</filter-mapping>

其主要就有两个配置targetFilterLifecycle(目标过滤器生命周期)和targetBeanName(目标bean的名称)

既然作为filter那么必然有一系列的方法,比如init,doFilter,destory,让我们跟踪进DelegatingFilterProxy类,

public class DelegatingFilterProxy extends GenericFilterBean

其继承了GenericFilterBean,并且发现DelegatingFilterProxy没有init方法,再跟踪进GenericFilterBean方法,继承关系如下

public abstract class GenericFilterBean implements Filter, BeanNameAware, EnvironmentAware,
		EnvironmentCapable, ServletContextAware, InitializingBean, DisposableBean

其只是实现了一些接口,所以GenericFilterBean为基类,因为继承关系,在DelegatingFilterProxy初始化时调用的是GenericFilterBean的init方法,init代码如下:

@Override
public final void init(FilterConfig filterConfig) throws ServletException {
    Assert.notNull(filterConfig, "FilterConfig must not be null");
    if (logger.isDebugEnabled()) {
	logger.debug("Initializing filter '" + filterConfig.getFilterName() + "'");
    }
	this.filterConfig = filterConfig;

	// 加载web.xml中的init参数,组装为PropertyValue对象,PropertyValues是对PropertyValue的封装
	PropertyValues pvs = new FilterConfigPropertyValues(filterConfig, this.requiredProperties);
	if (!pvs.isEmpty()) {
	    try {
	    BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
	    ResourceLoader resourceLoader = new ServletContextResourceLoader(filterConfig.getServletContext());
	    Environment env = this.environment;
		if (env == null) {
			env = new StandardServletEnvironment();
		}
		bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, env));
		initBeanWrapper(bw);
		bw.setPropertyValues(pvs, true);
	    }catch (BeansException ex) {
		String msg = "Failed to set bean properties on filter '" +
					filterConfig.getFilterName() + "': " + ex.getMessage();
		logger.error(msg, ex);
		throw new NestedServletException(msg, ex);
			}
		}

		// 子类DelegatingFilterProxy初始化
		initFilterBean();

	if (logger.isDebugEnabled()) {
	logger.debug("Filter '" + filterConfig.getFilterName() + "' configured successfully");
	}
	}

追踪到DelegatingFilterProxy的initFilterBean方法中,代码如下

protected void initFilterBean() throws ServletException {
	synchronized (this.delegateMonitor) {
		if (this.delegate == null) {
			// 这里判断targetBeanName是否配置,如果没有则用filter-name配置的值去spring容器中去查找具体的bean(这个值是在加载init的参数时通过反射设置的)
			if (this.targetBeanName == null) {
				this.targetBeanName = getFilterName();
			}
			// 这里是获取spring的IOC容器,为后续获取bean做准备
			WebApplicationContext wac = findWebApplicationContext();
			if (wac != null) {
				this.delegate = initDelegate(wac);//初始化filter
			}
		}
	}
}

在看initDelegate之前先看下,spring中shiro的配置bean:

<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">

initDelegate方法如下:

protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
//这里根据配置的targetBeanName去spring容器中获取bean,这里是通过shiroFilter去搜索ShiroFilterFactoryBean,这里检索到的是factoryBean,并不是普通的bean,这里返回的是由ShiroFilterFactoryBean的getObject方法得到的对象
	Filter delegate = wac.getBean(getTargetBeanName(), Filter.class);
	if (isTargetFilterLifecycle()) {
		//初始化Filter,AbstractFilter与AbstractShiroFilter
		delegate.init(getFilterConfig());
	}
	return delegate;
}
初始化完成,但是我在上面的init方法中并没有找到一些更有用的东西,这里delegate是SpringShiroFilter类,init调用的是父类

AbstractFilter的init方法,init如下:

public final void init(FilterConfig filterConfig) throws ServletException {
        setFilterConfig(filterConfig);
        try {
	 //子类实现,可以进行初始化
            onFilterConfigSet();
        } catch (Exception e) {
            if (e instanceof ServletException) {
                throw (ServletException) e;
            } else {
                if (log.isErrorEnabled()) {
                    log.error("Unable to start Filter: [" + e.getMessage() + "].", e);
                }
                throw new ServletException(e);
            }
        }
    }

这里并没有太多事,把filterConfig设置到本类中,并且让子类初始化,跟进onFilterConfigSet,在AbstractFilter的子类AbstractShiroFilter中:

protected final void onFilterConfigSet() throws Exception {
        //added in 1.2 for SHIRO-287:
        applyStaticSecurityManagerEnabledConfig();
        init();
        ensureSecurityManager();
        //added in 1.2 for SHIRO-287:
        if (isStaticSecurityManagerEnabled()) {
            SecurityUtils.setSecurityManager(getSecurityManager());
        }
    }

发现这里有init方法,但是不好意思这里是空的,init是没用的。可以看到这里只初始化了AbstractFilter与AbstractShiroFilter,那shiro的自己的拦截器是哪里加载的,拦截器链式怎么组装的呢?

真是一脸懵逼,开始我以为是在初次访问servlet时初始化,但是明显不对,因为调用时已经开始用拦截器链了。那是不是还有些地方初始化被丢下了?后来又跟踪了一遍,发现了确实有。

上面我们提到,我们在spring容器中获取factoryBean时,并不是获取这个Bean而是通过factoryBean的getObject方法返回的对象,就是在调用getObject方法构造对象时进行了shiro的拦截器加载,拦截器链组装。

这里就不带大家看怎么由getBean来调用getObject,无非就是取得Bean之后,判断该Bean是否为factoryBean,如果是通过反射调用该bean的getObject方法,然后返回对象。

下面重点看ShiroFilterFactoryBean这个类:

getObject方法:

public Object getObject() throws Exception {
        if (instance == null) {
            instance = createInstance();
        }
        return instance;
    }

createInstance方法,所有的工作都在这个方法进行,返回的是SpringShiroFilter类

protected AbstractShiroFilter createInstance() throws Exception {

        //获取安全管理器,在配置文件中配置的DefaultWebSecurityManager

        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();
        PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();
        chainResolver.setFilterChainManager(manager);

        return new SpringShiroFilter((WebSecurityManager) securityManager, chainResolver);
    }
下面重点看createFilterChainManager方法:
protected FilterChainManager createFilterChainManager() {
//这里用的是默认的,在创建的时候,构造方法内执行了addDefaultFilters方法,目的是从DefaultFilter这个枚举类
//中,加载设置好的默认拦截器,put到自己的filters中
        DefaultFilterChainManager manager = new DefaultFilterChainManager();
	//获取DefaultFilterChainManager的默认拦截器
        Map<String, Filter> defaultFilters = manager.getFilters();
        //设置全局参数
        for (Filter filter : defaultFilters.values()) {
		
            applyGlobalPropertiesIfNecessary(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);
                }
           
                manager.addFilter(name, filter, false);
            }
        }

        //build up the chains:
        //这里获取配置文件的filterChainDefinitions,以map形式存在,比如配置了/images/**=anon,则key为/images/**,value为anon
        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;
    }

接下来我们一步一步分析,先看new DefaultFilterChainManager():
public DefaultFilterChainManager() {
        this.filters = new LinkedHashMap<String, Filter>();
        this.filterChains = new LinkedHashMap<String, NamedFilterList>();
        addDefaultFilters(false);
    }
addDefaultFilters如下,加载了默认过滤器DefaultFilter
protected void addDefaultFilters(boolean init) {
        for (DefaultFilter defaultFilter : DefaultFilter.values()) {
            addFilter(defaultFilter.name(), defaultFilter.newInstance(), init, false);
        }
    }
我们看一下DefaultFilter类,是个枚举类,加载了如此多的拦截器,匿名,表单认证,退出等等:
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);
加载完之后,addFilter方法put进DefaultFilterChainManager的map属性filters中。
回到createFilterChainManager中,applyGlobalPropertiesIfNecessary方法如下
private void applyGlobalPropertiesIfNecessary(Filter filter) {
        applyLoginUrlIfNecessary(filter);
        applySuccessUrlIfNecessary(filter);
        applyUnauthorizedUrlIfNecessary(filter);
    }
上述方法设置了一些参数,都是对应配置文件中的三个属性loginUrl,successUrl,unauthorizedUrl
applyGlobalPropertiesIfNecessary中的三个方法大致作用相同
applyLoginUrlIfNecessary是为拦截器设置LoginUrl属性,设置之后,在登陆时如果没认证则访问配置路径,它的默认值是login.jsp。
applySuccessUrlIfNecessary大致相同,如果设置了,认证成功跳转配置参数,如果不设置默认访问“/”
applyUnauthorizedUrlIfNecessary和上面两个方法有一点不同,他没有默认值,不配置就不会跳转。
回到createFilterChainManager方法中继续分析,接下来就是自定义的filter,这里没什么好说的,把自定义的filter加入到manager的filters属性中去。
接下来是如下代码
 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);
            }
        }
getFilterChainDefinitionMap是获取配置文件中filterChainDefinitions属性,如配置如下:
<!-- 过虑器链定义,从上向下顺序执行,一般将/**放在最下边 -->
<property name="filterChainDefinitions">
	<value>
		/images/**=anon
		/js/**=anon
		/styles/**=anon
		<!-- 退出拦截,请求logout.action执行退出操作 -->
		/logout.action = logout
		<!-- user表示身份认证通过或通过记住我认证通过的可以访问 -->
		/** = authc
	</value>
</property>
getFilterChainDefinitionMap方法返回的是个map,key是url,value是拦截器名称
继续看代码createChain方法:
public void createChain(String chainName, String chainDefinition) {
        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 + "]");
        }

        //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]" }
        //
        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) {
            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]);
        }
    }
splitChainDefinition方法是用来分割拦截器字符的,就如注释一般:
如果配置为/** = authc, roles[admin,user], perms[file:edit],分割为一个数组就是[authc, roles[admin,user], perms[file:edit]]
按逗号分隔开,当然这里为什么要分割,拦截器链嘛,当匹配/**路径时,有三个拦截器要执行,这里是加载拦截器名称用的。
接下来就是对分割后的字符再次分割,这里只把字符分割为两个,按[来分割,比如上面的[authc,roles[admin,user],perms[file:edit]]会分割成[authc,null],[roles,admin,user](这里不是分割成三个,后面的admin,user是一个字符)
接下来看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 = 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).");
        }

        applyChainConfig(chainName, filter, chainSpecificFilterConfig);

        NamedFilterList chain = ensureChain(chainName);
        chain.add(filter);
    }
其中的getFilter方法是在加载的默认拦截器中通过名字查找。
applyChainConfig方法如下:
protected void applyChainConfig(String chainName,Filter filter,String chainSpecificFilterConfig) {
    if (log.isDebugEnabled()) {
        log.debug("Attempting to apply path [" + chainName + "] to filter [" + filter + "] " +
                "with config [" + chainSpecificFilterConfig + "]");
        }
    if (filter instanceof PathConfigProcessor) {
        ((PathConfigProcessor) filter).processPathConfig(chainName, chainSpecificFilterConfig);
    } else {
        if (StringUtils.hasText(chainSpecificFilterConfig)) {
        //they specified a filter configuration, but the Filter doesn't implement PathConfigProcessor
            //this is an erroneous config:
            String msg = "chainSpecificFilterConfig was specified, but the underlying " +
                  "Filter instance is not an 'instanceof' " +
                  PathConfigProcessor.class.getName() + ".  This is required if the filter is to accept " +
                 "chain-specific configuration.";
                throw new ConfigurationException(msg);
            }
        }
    }
上述中的processPathConfig方法:
public Filter processPathConfig(String path, String config) {
        String[] values = null;
        if (config != null) {
            values = split(config);
        }

        this.appliedPaths.put(path, values);
        return this;
    }
这个在类PathMatchingFilter中其实就是把路径和对应的配置加载到appliedPaths中(就目前来说我也不知道是干嘛用的,不急漫漫看)。
回到addToChain方法,看下ensureChain:
protected NamedFilterList ensureChain(String chainName) {
        NamedFilterList chain = getChain(chainName);
        if (chain == null) {
            chain = new SimpleNamedFilterList(chainName);
            this.filterChains.put(chainName, chain);
        }
        return chain;
    }
这里做的就是路径url和拦截器链的映射,具体实现是SimpleNamedFilterList,可以看出是个list,里面存储的是对应url所需要执行的拦截器。
简单介绍下上面的逻辑:首先通过名字getChain看是否加载过了这个NamedFilterList,如果没有加载,则新建一个SimpleNamedFilterList对象,然后把路径和这个list放进LinkedHashMap中(保证顺序),然后再在addToChain中add进拦截器,就保证路径和拦截器链的对应,如果一个路径配置了多了拦截器,只需调用add添加即可。
到此差不多就结束了,剩下最后一点,回到createInstance方法中只剩下下面代码:
        PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();
        chainResolver.setFilterChainManager(manager);
        return new SpringShiroFilter((WebSecurityManager) securityManager, chainResolver);
这里把DefaultFilterChainManager封装成了一个PathMatchingFilterChainResolver,里面只有两个方法getChain和pathMatches都是用来匹配路径的。这里不过多解释,并不属于初始化部分。
最后用这个包装类和安全管理器创建对象SpringShiroFilter
构造方法如下
protected SpringShiroFilter(WebSecurityManager webSecurityManager, FilterChainResolver resolver) {
        super();
        if (webSecurityManager == null) {
            throw new IllegalArgumentException("WebSecurityManager property cannot be null.");
        }
        setSecurityManager(webSecurityManager);
        if (resolver != null) {
            setFilterChainResolver(resolver);
        }
        }
这里的两个set方法把WebSecurityManager和FilterChainResolver设置到父类AbstractShiroFilter中。所有初始化完毕

总结:其实我们发现初始化就干了两件事一个是加载默认拦截器,加载到DefaultFilterChainManager的filters这个map中,按配置构造每个url的拦截器,加载到DefaultFilterChainManager的filterChains这个map中.(实际就是初始化了DefaultFilterChainManager,其余的都是小事)


















  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
web.xml配置 因为我们是与spring进行集成的,而spring的基本就是web项目的xml文件。所以我们在web.xml中配置shiros的过滤拦截。正常情况下,我们需要将shiro的filter配置在所有的filter前面,当然和encodingFilter这个filter是不区分前后的。因为两者互相不影响的。spring-shiro.xml 这里我们将来看看spring-shiro.xml的配置,这里我采取倒叙的方式讲解,我觉的倒叙更加的有助于我们理解代码。首先我们还记得在web.xml中配置的那个filter吧,名字shiroFilter,对spring-shiro.xml配置文件就是通过这个filter展开的。首先我们在web.xml配置的过滤器实际上是配置ShiroFilterFactoryBean,所以在这里需要将ShiroFilterFactoryBean定义为shiroFilter <!-- Shiro的核心安全接口,这个属性是必须的 --> <!-- 要求登录时的链接(可根据项目的URL进行替换),非必须的属性,默认会自动寻找Web工程根目录下的"/login.html"页面 --> <!-- 登录成功后要跳转的连接 --> <!-- 用户访问未对其授权的资源时,所显示的连接 --> <!-- 若想更明显的测试此属性可以修改它的值,如unauthor.jsp,然后用[玄玉]登录后访问/admin/listUser.jsp就看见浏览器会显示unauthor.jsp --> <!-- Shiro连接约束配置,即过滤链的定义 --> <!-- 此处可配合我的这篇文章来理解各个过滤连的作用http://blog.csdn.net/jadyer/article/details/12172839 --> <!-- 下面value值的第一个'/'代表的路径是相对于HttpServletRequest.getContextPath()的值来的 --> <!-- anon:它对应的过滤器里面是空的,什么都没做,这里.do和.jsp后面的*表示参数,比方说login.jsp?main这种 --> <!-- authc:该过滤器下的页面必须验证后才能访问,它是Shiro内置的一个拦截器org.apache.shiro.web.filter.authc.FormAuthenticationFilter --> /statics/**=anon /login.html=anon /sys/schedule.html=perms[sys:schedule:save] /sys/login=anon /captcha.jpg=anon /**=authc

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jackson陈

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值