转载: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 怎么从xml
到BeanFactory
加载 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后面的故找不到springSecurityFilterChain
的BeanDefinition
故将资源加载依托在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>