`NoSuchBeanDefinitionException` after migration from `antMatchers` to `requestMatchers` (5.8.0)
See original GitHub issueHi,
I upgraded from 5.7.x to 5.8.0 and replaced the deprecated antMatchers(String...)
with requestMatchers(String...)
as suggested by the Javadoc, but now i’m getting the following error on application startup (note the weird exception message “No bean named 'A Bean named…”):
org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'A Bean named mvcHandlerMappingIntrospector of type org.springframework.web.servlet.handler.HandlerMappingIntrospector is required to use MvcRequestMatcher. Please ensure Spring Security & Spring MVC are configured in a shared ApplicationContext.' available
at org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry.createMvcMatchers(AbstractRequestMatcherRegistry.java:187)
at org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry.requestMatchers(AbstractRequestMatcherRegistry.java:302)
at org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry.requestMatchers(AbstractRequestMatcherRegistry.java:329)
at com.example.spring.security.SecurityConfig.filterChain(SecurityConfig.java:36)
at com.example.spring.security.SecurityConfig$$EnhancerBySpringCGLIB$$d314e6bc.CGLIB$filterChain$0(<generated>)
at com.example.spring.security.SecurityConfig$$EnhancerBySpringCGLIB$$d314e6bc$$FastClassBySpringCGLIB$$f1e371fd.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:244)
at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:331)
at com.example.spring.security.SecurityConfig$$EnhancerBySpringCGLIB$$d314e6bc.filterChain(<generated>)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154)
SpringWebAppInitializer.java
public class SpringWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[] { RootConfig.class, SecurityConfig.class };
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] { ServletConfig.class };
}
@Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
public static class SecurityWebAppInitializer extends AbstractSecurityWebApplicationInitializer {}
}
RootConfig.java
@Configuration
@ComponentScan
public class RootConfig {}
SecurityConfig.java
@Configuration
@ComponentScan
@EnableWebSecurity
@EnableMethodSecurity(securedEnabled = true)
public class SecurityConfig implements ApplicationContextAware {
private ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext context) {
this.context = context;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.jee().authenticatedUserDetailsService(token -> context.getBean(UserDetailsService.class).loadUserDetails(token)).mappableAuthorities(UserRole.stream().map(UserRole::getName).collect(Collectors.toSet())) // lambda required for warm context refresh
.and().authorizeHttpRequests().requestMatchers("/admin/**").hasAuthority(RoleNames.ROLE_ADMIN) // this doesn't work but antMatchers did
.and().csrf().disable().logout().logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler(HttpStatus.NO_CONTENT));
return http.build();
}
}
ServletConfig.java
@Configuration
@ComponentScan
@EnableWebMvc
@EnableAspectJAutoProxy
public class ServletConfig implements WebMvcConfigurer {}
I noted that if I return null
from getRootConfigClasses()
and move both RootConfig.class
& SecurityConfig.class
to the array returned by getServletConfigClasses()
, the application starts, but I don’t understand the reason behind this, and I would like to keep separated the beans of root and servlet contexts.
Note also that moving only SecurityConfig.class
from the root config to the servlet config produces another exception on application startup:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'springSecurityFilterChain' available
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:874)
at org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1358)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:309)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:213)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1160)
at org.springframework.web.filter.DelegatingFilterProxy.initDelegate(DelegatingFilterProxy.java:334)
at org.springframework.web.filter.DelegatingFilterProxy.initFilterBean(DelegatingFilterProxy.java:239)
at org.springframework.web.filter.GenericFilterBean.init(GenericFilterBean.java:239)
at org.apache.catalina.core.ApplicationFilterConfig.initFilter(ApplicationFilterConfig.java:272)
at org.apache.catalina.core.ApplicationFilterConfig.<init>(ApplicationFilterConfig.java:106)
at org.apache.catalina.core.StandardContext.filterStart(StandardContext.java:4609)
at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5248)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1393)
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1383)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75)
at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:140)
at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:916)
at org.apache.catalina.core.StandardHost.startInternal(StandardHost.java:835)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1393)
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1383)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75)
at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:140)
at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:916)
at org.apache.catalina.core.StandardEngine.startInternal(StandardEngine.java:265)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
at org.apache.catalina.core.StandardService.startInternal(StandardService.java:430)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
at org.apache.catalina.core.StandardServer.startInternal(StandardServer.java:930)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
at org.apache.catalina.startup.Catalina.start(Catalina.java:772)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:345)
at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:476)
To Reproduce, please download this sample app and deploy it into Tomcat 9.0.
Thanks.
Issue Analytics
- State:
- Created 10 months ago
- Comments:9 (5 by maintainers)
Hi, @albertus82. After reaching out to the team and getting some good explanations, especially from @rwinch and @dsyer:
The
getRootConfigClasses
andgetServletConfigClasses
are meant to be used by applications with more than one Servlet that depends on a shared set of service beans, in such cases, it could make sense to have shared services in the root context so they are only created once for all servlet instances. This setup was common a long time ago (XML days) but not common now, even with WAR deployments.Therefore, this was before the concept of
MvcRequestMatcher
, as I mentioned, the documentation states that you need the Spring Security and Spring MVC beans to be visible to each other. If you have multiple Servlets it then becomes complicated because you run into a situation where you likely need aspringSecurityFilterChain
for each servlet so that you can ensure the routing of Security and the servlets align. There are better ways to handle things than having more than one servlet (DispatcherServlet
is meant to be the entry point to everything and it dispatches out to various controllers rather than having lots ofDispatcherServlet
s).With that said, if such a setup makes sense for you, you can configure your initializer like this:
And override AbstractSecurityWebApplicationInitializer.getDispatcherWebApplicationContextSuffix() to return
AbstractDispatcherServletInitializer.DEFAULT_SERVLET_NAME
, so that Spring Security is loaded from the DispatcherServlet’s ApplicationContext.The final code for your initializer would be:
I hope that the explanation makes sense to you.
Something might be different then, with that setup I get
Authenticated user: ADMIN - Roles: [ROLE_ADMIN]
in the root path.That was my first discussion with @rwinch when designing this feature. It is designed this way because if you have Spring WebMVC in the classpath but somehow don’t have the
mvcHandlerMappingIntrospector
bean, that means that Spring Security Configuration is not in the sameApplicationContext
as yourDispatcherServlet
, and theMvcRequestMatcher
needs that bean, see here. If you do not want to useMvcRequestMatcher
, an explicit configuration is needed.That said, I brought this matter to the team’s attention and we are looking into it.