• 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>
    
  • 相关阅读:
    【Git】时光机命令—Git命令
    【Vue.js】vue引入组件报错:该组件未注册?
    【CSS】flex布局初认识
    【Vue.js】基于vue的实时搜索,在结果中高亮显示关键词
    MyBatis介绍并解决jdbc编程的问题
    MyBatis中 #{} 和 ${}的区别
    Mybatis执行操作时控制台日志中显示sql语句
    Mybatis中常见操作(基本操作+动态sql+sql片段+关联映射(resultMap))
    Spring配置初始化和销毁的方法
    平时工作常用linux命令总结
  • 原文地址:https://www.cnblogs.com/land-fill/p/13451309.html
Copyright © 2020-2023  润新知