DelegatingFilterProxy


前言

最近可算把一个项目整完了,然后最近打算来消化下项目中不懂的地方。项目中用到了spring-security来做验证授权,这一块是自己的知识盲区。所以先就来攻克这一块。

我们都知道在使用spring-security的时候都需要在web.xml做如下配置,这样的配置就表明了spring-security实际上是通过过滤器来实现的,很显然利用Filter确实是最好的选择。那么到底是如何通过以下配置,让过滤器和spring-security联系起来的呢,我们就来探讨一下,找到执行这一块的过程,接着,我们就使用spring-security的官方文档来一边学习一边看源码。

  <filter>
  <filter-name>springSecurityFilterChain</filter-name>
  <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
  </filter>
  <filter-mapping>
  <filter-name>springSecurityFilterChain</filter-name>
  <url-pattern>/*</url-pattern>
  </filter-mapping>

DelegatingFilterProxy

这个类的作用就和他的名字一样,他就是一个起代理作用的过滤器,而真正的拦截器当然就是是spring-security里面的配置咯。但是我们知道,过滤器是由Tomcat来解析加载的,跟Spring容器是没有关系的。

所以我们就带着两个问题来看看这个类:

0.该类是如何完成代理。

1.是如何将过滤器和Spring容器结合起来的。

我们看源码:

先看父类GenericFilterBean

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

他的父类GenericFilterBean实现了很多接口,其中我们最熟悉的就是InitializingBean了。我们知道注入到Spring的Bean,在实例化之后,如果有实现InitializingBean接口的,会调用他的afterPropertiesSet方法。

而我们也注意到虽然GenericFilterBean是一个抽象类,但是他显式的实现了Filter的init方法,而子类DelegatingFilterProxy并没有显式的实现init方法,所以,也就是说当我们向tomcat注册了DelegatingFilterProxy过滤器后,会调用该监过滤器的init方法,而实际上调用的是父类的init方法,我们来看看这个方法做了什么。

    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;

        // Set bean properties from init parameters.
        try {
            PropertyValues pvs = new FilterConfigPropertyValues(filterConfig, this.requiredProperties);
            BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
            ResourceLoader resourceLoader = new ServletContextResourceLoader(filterConfig.getServletContext());
            bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.environment));
            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);
        }

        // Let subclasses do whatever initialization they like.
        initFilterBean();

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

这个init()方法主要前面很长一段代码,直到 initFilterBean();之前,都是用来获取Filter配置中的参数即init-param value的配置。我们需要关注的就是initFilterBean();方法,这个方法在父类中是一个抽象方法,具体的实现在

    @Override
    protected void initFilterBean() throws ServletException {
        synchronized (this.delegateMonitor) {
            if (this.delegate == null) {
                // If no target bean name specified, use filter name.
                if (this.targetBeanName == null) {
                    this.targetBeanName = getFilterName();
                }
                // Fetch Spring root application context and initialize the delegate early,
                // if possible. If the root application context will be started after this
                // filter proxy, we'll have to resort to lazy initialization.
                WebApplicationContext wac = findWebApplicationContext();
                if (wac != null) {
                    this.delegate = initDelegate(wac);
                }
            }
        }
    }

我们分析下这个方法

0.首先要做一个同步操作。

1.然后进入同步块,如果当前的代理对象为null,查看targetBeanName对象是否为null,targetBeanName属性可以通过在web.xml中的filter中的init-param中配置。一般我们不显式的配置。

2.如果我们没有显式的配置的话,就调用getFilterName方法来得到targetBeanName:

    protected final String getFilterName() {
        return (this.filterConfig != null ? this.filterConfig.getFilterName() : this.beanName);
    }

方法很简单,如果这时我们没有在filter初始化参数中配置beanName的话,我们就取当前filterName,filterName是filter配置中必须要配置的。

3.得到当前的spring上下文,因为当我们通过ContextLoaderListener这个监听器去初始化Spring容器的时候,会把根上下文当做一个attribute注入到servletContext(即servlet上下文)中,key为WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,value为根上下文,而我们在过滤器中又能通过FIlterConfig.getServletContext去得到ServletContext,所以我们也就顺理成章的能得到spring上下文了。

4.当前上下文不为空的时候,调用initDelegate(wac);来得到真正用来执行的过滤器。

    protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
        Filter delegate = wac.getBean(getTargetBeanName(), Filter.class);
        if (isTargetFilterLifecycle()) {
            delegate.init(getFilterConfig());
        }
        return delegate;
    }

这个方法很简单,就是通过限定BeanName以及Bean的类型为Filter来获取bean,而这里的BeanName就是targetName,当没有在filter配置中配置的时候,我们直接用的是filterName,而我们这里的配置的filterName是springSecurityFilterChain。看到这里,你肯定能得出一个结论,那就是如果你不在filter里配置targetBeanName或者beanName的时候,你的filterName就必须是springSecurityFilterChain,否则,肯定就无法在Spring找到delegate对象了,而BeanName为springSecurityFilterChain的Bean对象肯定在我们配置spring-security配置的时候以默认的方式注入到了spring,只有这样才说的通。

而对于DelegatingFilterProxy的dofilter方法,我们不用多看,猜也猜得到内部是调用delegate.dofilter方法。

那文章的最后,我们就来找一下到底这个BeanName为springSecurityFilterChain的Bean对象是哪个把,也算为我们之后阅读spring-security模块的代码开个头。

我们知道spring-security的配置是如下的形式:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/security
        http://www.springframework.org/schema/security/spring-security.xsd">
<http>
<intercept-url pattern="/**" access="hasRole('USER')"></intercept-url>
</http>
</beans>

而且会使用spring-security命名空间。很明显又是使用了命名空间来解析xml配置。这里我们不赘述整个过程了,我们直接找到解析http节点的BeanDefinitionParser实现类。

这个类就是org.springframework.security.config.http.HttpSecurityBeanDefinitionParser,我们直接看他的parse方法。

    public BeanDefinition parse(Element element, ParserContext pc) {
        CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(
                element.getTagName(), pc.extractSource(element));
        pc.pushContainingComponent(compositeDef);

        registerFilterChainProxyIfNecessary(pc, pc.extractSource(element));

        // Obtain the filter chains and add the new chain to it
        BeanDefinition listFactoryBean = pc.getRegistry().getBeanDefinition(
                BeanIds.FILTER_CHAINS);
        List<BeanReference> filterChains = (List<BeanReference>) listFactoryBean
                .getPropertyValues().getPropertyValue("sourceList").getValue();

        filterChains.add(createFilterChain(element, pc));

        pc.popAndRegisterContainingComponent();
        return null;
    }

这个方法的具体实现我们先不分析,我们看到他的一个子方法

registerFilterChainProxyIfNecessary

    static void registerFilterChainProxyIfNecessary(ParserContext pc, Object source) {
        if (pc.getRegistry().containsBeanDefinition(BeanIds.FILTER_CHAIN_PROXY)) {
            return;
        }
        // Not already registered, so register the list of filter chains and the
        // FilterChainProxy
        BeanDefinition listFactoryBean = new RootBeanDefinition(ListFactoryBean.class);
        listFactoryBean.getPropertyValues().add("sourceList", new ManagedList());
        pc.registerBeanComponent(new BeanComponentDefinition(listFactoryBean,
                BeanIds.FILTER_CHAINS));

        BeanDefinitionBuilder fcpBldr = BeanDefinitionBuilder
                .rootBeanDefinition(FilterChainProxy.class);
        fcpBldr.getRawBeanDefinition().setSource(source);
        fcpBldr.addConstructorArgReference(BeanIds.FILTER_CHAINS);
        fcpBldr.addPropertyValue("filterChainValidator", new RootBeanDefinition(
                DefaultFilterChainValidator.class));
        BeanDefinition fcpBean = fcpBldr.getBeanDefinition();
        pc.registerBeanComponent(new BeanComponentDefinition(fcpBean,
                BeanIds.FILTER_CHAIN_PROXY));
        pc.getRegistry().registerAlias(BeanIds.FILTER_CHAIN_PROXY,
                BeanIds.SPRING_SECURITY_FILTER_CHAIN);
    }

}

我们看到倒数第二行代码,将一个Bean注入到spring中,名字为BeanIds.FILTER_CHAIN_PROXY。

接着我们看到方法的最后一行代码,给这个Bean赋一个别名BeanIds.SPRING_SECURITY_FILTER_CHAIN,而对应的值就是springSecurityFilterChain

/** External alias for FilterChainProxy bean, for use in web.xml files */
    public static final String SPRING_SECURITY_FILTER_CHAIN = "springSecurityFilterChain";

所以这里也就完成了DelegatingFilterProxy中delegate对象注入到spring容器的过程。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值