Spring Security使用报错 No bean named 'springSecurityFilterChain' is defined

转载:https://gist.github.com/linlihai/10219184#file-gistfile1-md

近日来自己想基于maven搞一个有多子模块的archetype,就用比较流行的Spring mvc.archetype
闲来无事加上Spring security可好?
于是乎加上Spring security之后应用起不来了,日志如下:

org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'springSecurityFilterChain' is defined
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:641)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1159)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:282)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200)
	at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:979)
	at org.springframework.web.filter.DelegatingFilterProxy.initDelegate(DelegatingFilterProxy.java:324)
	at org.springframework.web.filter.DelegatingFilterProxy.initFilterBean(DelegatingFilterProxy.java:235)
	at org.springframework.web.filter.GenericFilterBean.init(GenericFilterBean.java:199)
	at org.eclipse.jetty.servlet.FilterHolder.doStart(FilterHolder.java:99)

网上google了一下,碰到这个问题的人很多,但是解决方案却没有一个说法,只能自己动手了.

什么时候定义的'springSecurityFilterChain'

日志报的是No bean named 'springSecurityFilterChain' is defined,那什么时候定义的springSecurityFilterChain?

回答问题之前我们来回忆下Spriong IOC 怎么从xmlBeanFactory

加载 Schema

Srping 启动的时候 会去jar的META-INF下找Spring.schemas properties文件里面定义了 schema 到本地jar路径的一个映射关系

解析Xml

找到本地schema(dom树的描述结构), 去解析xml文件,校验配置是否合法,然后解析成一棵Dom树

注册 Handler

在jar的META-INF下找Spring.handlers properties文件里面定义了NamesapeceHandler, handler又注册了BeanDefinitionParser

Dom ---> BeanDefinition

每一个dom的节点都对应了一个BeanDefinitionParser,由他来解析成BeanDefinition

对应到这个问题,找到org.springframework.security:spring-security-config下的Spring.handlers,发现NamesapeceHandler是SecurityNamespaceHandler.而SecurityNamespaceHandler注册一堆BeanDefinitionParser

private void loadParsers() {
        // Parsers
        parsers.put(Elements.LDAP_PROVIDER, new LdapProviderBeanDefinitionParser());
        parsers.put(Elements.LDAP_SERVER, new LdapServerBeanDefinitionParser());
        parsers.put(Elements.LDAP_USER_SERVICE, new LdapUserServiceBeanDefinitionParser());
        parsers.put(Elements.USER_SERVICE, new UserServiceBeanDefinitionParser());
        parsers.put(Elements.JDBC_USER_SERVICE, new JdbcUserServiceBeanDefinitionParser());
        parsers.put(Elements.AUTHENTICATION_PROVIDER, new AuthenticationProviderBeanDefinitionParser());
        parsers.put(Elements.GLOBAL_METHOD_SECURITY, new GlobalMethodSecurityBeanDefinitionParser());
        parsers.put(Elements.AUTHENTICATION_MANAGER, new AuthenticationManagerBeanDefinitionParser());
        parsers.put(Elements.METHOD_SECURITY_METADATA_SOURCE, new MethodSecurityMetadataSourceBeanDefinitionParser());

        //当前的类加载器是否能够加载到 "org.springframework.security.web.FilterChainProxy"
        //来判断是否Web Application
        if(ClassUtils.isPresent(FILTER_CHAIN_PROXY_CLASSNAME, getClass().getClassLoader())) {
            parsers.put(Elements.DEBUG, new DebugBeanDefinitionParser());
            // 关键的http解析器
            parsers.put(Elements.HTTP, new HttpSecurityBeanDefinitionParser());
            parsers.put(Elements.HTTP_FIREWALL, new HttpFirewallBeanDefinitionParser());
            parsers.put(Elements.FILTER_INVOCATION_DEFINITION_SOURCE, new FilterInvocationSecurityMetadataSourceParser());
            parsers.put(Elements.FILTER_SECURITY_METADATA_SOURCE, new FilterInvocationSecurityMetadataSourceParser());
            parsers.put(Elements.FILTER_CHAIN, new FilterChainBeanDefinitionParser());
            filterChainMapBDD = new FilterChainMapBeanDefinitionDecorator();
        }
    }

HttpSecurityBeanDefinitionParser相关代码

static void registerFilterChainProxyIfNecessary(ParserContext pc, Object source) {
	// 如果已经注册过了 就直接返回
        if (pc.getRegistry().containsBeanDefinition(BeanIds.FILTER_CHAIN_PROXY)) {
            return;
        }
        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));
        // BeanIds.FILTER_CHAIN_PROXY注册了一个 : org.springframework.security.filterChainProxy 
        // BeanIds.SPRING_SECURITY_FILTER_CHAIN :  springSecurityFilterChain
        // 为BeanIds.FILTER_CHAIN_PROXY注册了一个springSecurityFilterChain的别名
        pc.getRegistry().registerAlias(BeanIds.FILTER_CHAIN_PROXY, BeanIds.SPRING_SECURITY_FILTER_CHAIN);
    }

看到了这里,如果说在加载Filter之前,Spring解析security.xml文件的话那么 Spring的beanDefinitionMap应该是能找的到这个BeanDefinition才对.
为了证实这点,debug了下源码

/**
 *  DefaultListableBeanFactory 
 */
public BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException {
		BeanDefinition bd = this.beanDefinitionMap.get(beanName);
		if (bd == null) {
			if (this.logger.isTraceEnabled()) {
				this.logger.trace("No bean named '" + beanName + "' found in " + this);
			}
			throw new NoSuchBeanDefinitionException(beanName);
		}
		return bd;
	}

源码中的 beanDefinitionMap.size()==0,结果已经很明朗了Filter的加载在Security.xml之前了.
来看下web.xml的配置吧.

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0">

    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-*.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>*.htm</url-pattern>
    </servlet-mapping>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <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>
</web-app>

servlet 规范

Servlet规范中 web.xml内加载的顺序是 context-param-->listener-->filter-->servlet.

在web.xml的配置文件的加载依托给了DispatcherServlet,而servlet的加载顺序是在filter后面的故找不到springSecurityFilterChainBeanDefinition

故将资源加载依托在context-param,问题就解决了

具体操作:

在spring-security.xml中注册配置类

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       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.xsd">


    <bean name="webSecurityConfig" class="cc.landfill.crowd.mvc.config.WebSecurityConfig"/>
</beans>

在web.xml中的context 中加载该xml文件,后加载的delegatingFilterProxy就能在容器中找到springSecurityFilterChain

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:spring-persist-*.xml,classpath:spring-security.xml</param-value>
</context-param>
原文地址:https://www.cnblogs.com/land-fill/p/13451309.html