Closed spring-projects-issues closed 6 years ago
José María Sola Durán commented
Only occurs when use @RestController
in Spring MVC because I think that Spring auto-config the CORS support.
Rossen Stoyanchev commented
You're probably using the mvcMatcher
in Spring Security which aligns Spring Security with Spring MVC pattern matching configuration. However Spring Security is in the "parent", root context while Spring MVC config is in the "child", Servlet context. So Spring Security can't find the Spring MVC mapping configuration. This is why the error message says "Please ensure Spring Security & Spring MVC are configured in a shared ApplicationContext."
You can combine AppConfig and SpringMVCRestConfig and return both as your root config. That's all that should be necessary sine the MVC Java config includes an HandlerMappingIntrospector
bean (this is in WebMvcConfigurationSupport
.
For more details see the Spring Security docs https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#mvc-requestmatcher.
José María Sola Durán commented
The Spring Security Context is declare in class SpringMVCRestConfig. This is the 'child' context of the parent root context 'AppConfig'.
Below, the first lines of SpringMVCRestConfig class:
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = { "com.example.customproject.api.mvc.*" })
@Import({ SpringSecurityConfig.class })
public class SpringMVCRestConfig implements WebMvcConfigurer {
[...]
This is de SpringSecurityConfig class:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(jsr250Enabled = true, prePostEnabled = true, securedEnabled = true, proxyTargetClass=true)
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService());
auth.authenticationProvider(authenticationProvider());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().httpBasic().and().authorizeRequests().antMatchers(HttpMethod.OPTIONS).permitAll().
anyRequest().authenticated().and().csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
}
@Bean
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService());
authenticationProvider.setPasswordEncoder(bCryptPasswordEncoder());
return authenticationProvider;
}
@Bean
@Override
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
@Bean
public UserDetailsService userDetailsService() {
return new UserDetailsServiceImpl();
}
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder(11);
}
}
In the previous versión of Spring (v4.2.x), yes it worked! Thank you for the supports!
Rossen Stoyanchev commented
This is odd because you will see that WebMvcConfigurationSupport
declares an mvcHandlerMappingIntrospector
bean. Is it possible that something is causing SpringSecurityConfig to be loaded from the root context, e.g. via a component scan and since SpringSecurityConfig is marked with @Configuration
? Also could you confirm the version of Spring Security?
/cc robwinch if you have any other ideas?
José María Sola Durán commented
In the Spring Framework v4.3.x my @Configuration
class SpringMVCRestConfig extends WebMvcConfigurerAdapter. However, in the Spring Framework v5.0.0.RELEASE the class WebMvcConfigurerAdapter is deprecated. Then, SpringMVCRestConfig implements the interface, with default methods, WebMvcConfigurer. I don't use WebMvcConfigurationSupport. Also, I try that SpringMVCRestConfig extends WebMvcConfigurationSupport, but it isn't work either.
The version of Spring Security is 5.0.0.RELEASE. The only way it works is add manually in the AppConfig class (root context) @Bean
'mvcHandlerMappingIntrospector'.
@Configuration
public class AppConfig {
[...]
@Bean(name = "mvcHandlerMappingIntrospector")
public HandlerMappingIntrospector mvcHandlerMappingIntrospector() {
return new HandlerMappingIntrospector();
}
}
I think that the Spring Security context start with 'child' context because, in first time Spring load root context, then create a HandlerMappingIntrospector bean, and after start 'child' DispacherServlet context and then, this one does find the HandlerMappingIntrospector bean.
Rossen Stoyanchev commented
It's absolutely fine that SpringMVCRestConfig implements WebMvcConfigurer. The deprecation of WebMvcConfigurerAdapter has no impact whatsoever -- it simply takes advantage of Java 8 default methods on an interface. You do not need to extend WebMvcConfigurationSupport
as you are using @EnableWebMvc
which, if you look inside its declaration, you'll see that it imports WebMvcConfigurationSupport
(or rather a sub-class of it). None of this explains the issue.
The only way it works is add manually in the AppConfig class (root context)
@Bean
'mvcHandlerMappingIntrospector'.
This means that Spring Security configuration is somehow loaded in the root context. Can you check what's in your AppConfig.class
? It must be pulling in Spring Security configuration somehow.
The only way it works is add manually in the AppConfig class (root context)
@Bean
'mvcHandlerMappingIntrospector'.
Just a warning, please don't do that in production. In other words don't ignore the second part of the error message:
"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."
José María Sola Durán commented
The fact that the security context starts together with "SpringMVCRestConfig" is that if in AppConfig the HandlerMappingIntrospector bean is added manually, the security context does not throw any exception when starting. In the console log of Tomcat, the trace reflects the following: 1.) Start the root context (A HandlerMappingIntrospector bean has just been instantiated). 2.) Start the context of DispacherServlet, and with it, the security context of Spring Framework
If the HandlerMappingIntrospector class is not added to AppConfig, then: 1.) Start the root context (A HandlerMappingIntrospector is not instantiated) 2.) When starting the context of DispacherServlet, and with it, the security context of Spring Framework, the exception that I indicated in previous comments is throwing.
The same code with this versions working perfectly: 1.) Spring Framework --> 4.3.13.RELEASE 2.) Spring Security --> 4.2.3.RELEASE
I think that in Spring Beans artifact (spring-beans-5.0.x.RELEASE.jar) something has changed and then, the order to load the beans is distinct.
Thank you! Happy New Year!
Rossen Stoyanchev commented
SpringMVCRestConfig
has @EnableWebMvc
which imports DelegatingWebMvcConfiguration
which automatically declares a HandlerMappingIntrospector
bean (see declaration). The SpringSecrityConfig
which is imported from SpringMVCRestConfig
should be able to find that bean just fine. All of this has no impact on the "root" context AppConfig
.
The only way to explain the error coming from AppConfig
is that you are importing Spring Security configuration from AppConfig
(which means you've configured Spring Security in both root and child contexts!) so my advice still stands:
You can combine AppConfig and SpringMVCRestConfig and return both as your root config. That's all that should be necessary sine the MVC Java config includes an HandlerMappingIntrospector bean (this is in WebMvcConfigurationSupport.
For more details see the Spring Security docs https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#mvc-requestmatcher.
Rossen Stoyanchev commented
Resolving for now as we've discussed this enough. You can comment further but if you wish to re-open please provide a sample app to demonstrate the issue.
José María Sola Durán commented
You can download an example code in this link: https://we.tl/1V2GCUuqyJ
Please, you must download code as soon as possible because the will expire. With this code, you can reproduce the error.
I think that the bug is in AbstractAnnotationConfigDispatcherServletInitializer
. If you change the class net.ddns.jmsola.customproject.api.WebAppInitializer
with the code below it's working!
package net.ddns.jmsola.customproject.api;
import java.util.EnumSet;
import javax.servlet.DispatcherType;
import javax.servlet.FilterRegistration;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.filter.DelegatingFilterProxy;
import org.springframework.web.servlet.DispatcherServlet;
import net.ddns.jmsola.customproject.api.config.AppConfig;
import net.ddns.jmsola.customproject.api.config.SpringMVCRestConfig;
public class WebAppInitializer implements WebApplicationInitializer {
private static final String SPRING_SECURITY_FILTER_CHAIN = "springSecurityFilterChain";
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext();
rootAppContext.register(AppConfig.class);
servletContext.addListener(new ContextLoaderListener(rootAppContext));
AnnotationConfigWebApplicationContext mvcContext = new AnnotationConfigWebApplicationContext();
rootAppContext.register(SpringMVCRestConfig.class);
DispatcherServlet dispacherServlet = servletContext.createServlet(DispatcherServlet.class);
dispacherServlet.setApplicationContext(mvcContext);
ServletRegistration.Dynamic registrationDispacherServlet = servletContext.addServlet("dispacherServlet",
dispacherServlet);
registrationDispacherServlet.setLoadOnStartup(1);
registrationDispacherServlet.addMapping("/api/*");
DelegatingFilterProxy springSecurityFilterChain = new DelegatingFilterProxy(SPRING_SECURITY_FILTER_CHAIN);
FilterRegistration.Dynamic registration = servletContext.addFilter(SPRING_SECURITY_FILTER_CHAIN,
springSecurityFilterChain);
if (registration == null) {
throw new IllegalStateException("Duplicate Filter registration for '" + SPRING_SECURITY_FILTER_CHAIN
+ "'. Check to ensure the Filter is only configured once.");
}
EnumSet<DispatcherType> dispatcherTypes = EnumSet.of(DispatcherType.REQUEST, DispatcherType.ERROR);
registration.addMappingForUrlPatterns(dispatcherTypes, false, "/*");
}
}
Rossen Stoyanchev commented
Thanks for the sample.
Just as I suspected the following in your AppConfig
in turn loads SpringSecurityConfig
(see Javadoc of @Configuration
with component scanning):
@ComponentScan(basePackages = { "net.ddns.jmsola.customproject.api.security.*" })
I moved SpringSecurityConfig
into the config package next to AppConfig
(because it's already explicitly imported from the web config). I then changed WebApplicationInitializer
to load all configuration from the root context:
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] {AppConfig.class, SpringMVCRestConfig.class};
}
@Override
protected Class<?>[] getServletConfigClasses() {
return null;
}
It now works as expected.
Why did I have to make such changes when it was working fine before? Your application wasn't configured correctly before, you just didn't get any error messages. That's the difference. You were loading Spring Security configuration in two places -- both root and child context, but only the one from the root context is really used and plugged in through the DelegatingProxyFilter. In addition the mvcMatcher in Spring Security couldn't have worked correctly either.
Eric Deandrea commented
Sorry I'm coming late to this. I've been working on a project where I just moved from Spring Boot 1.5.9 to 1.5.10 (which also upgraded spring-webmvc from 4.3.13 to 4.3.14 and spring-security from 4.2.3 to 4.2.4. Unlike the examples above I don't have any initializers or anything like that, nor do I have any classes that use @EnableWebMvc
or that implement WebMvcConfigurer
.
I'm actually seeing this issue in some unit tests where I am testing my security configuration. I am building a library that is used by applications. Upgrading our reference application from 1.5.9 to 1.5.10 seems to be fine, but our unit tests for our library are now failing.
My test class simply uses
@RunWith(SpringRunner.class)
@ActiveProfiles("header-user-filter-tests")
@WebAppConfiguration
@SpringBootTest(classes = { MyTestClass.Config.class })
at the class level and then a nested inner Config class:
@Configuration
@ImportAutoConfiguration({ SomeCustomAutoConfigurationClass.class, SomeCustomSecurityAutoConfigurationClass.class, org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration.class })
@Profile("header-user-filter-tests")
public static class Config {
@Bean
public UserDetailsService userDetailsService() {
UserDetailsService userDetailsService = Mockito.mock(UserDetailsService.class);
// @formatter:off
BDDMockito
.given(userDetailsService.loadUserByUsername(BDDMockito.anyString()))
.willReturn(User.withUsername("user").password("n/a").authorities(new GrantedAuthority[0]).build());
// @formatter:off
return userDetailsService;
}
}
The SomeCustomAutoConfigurationClass just does a component scan of some of our library classes and also has an @ConfigurationProperties
annotation.
The SomeCustomSecurityAutoConfigurationClass class has the @EnableWebSecurity
annotation as well as it extends WebSecurityConfigurerAdapter
.
I've tried a number of different things, including manually defining the mvcHandlerMappingIntrospector bean in my configuration but nothing seems to work.
Any help would be appreciated!
Rossen Stoyanchev commented
I'm not sure what would be the best approach in this (test) scenario but let's not make this ticket, which is a related but clearly different scenario, any longer than it already is. Please create a ticket under Spring Security.
Eric Deandrea commented
Will do - https://github.com/spring-projects/spring-security/issues/4995.
José María Sola Durán opened SPR-16301 and commented
I start Spring MVC Context with this WebApplicationInitializer:
Also, I start Spring Security Context. When the web app init context, I obtain the next exception:
I solve this error, if I add the next code in the root app context:
Affects: 5.0.2