Jon von Kampen (Migrated from SEC-2942) said:
I'm writing a Spring MVC application that uses Spring Security. The docs state that adding @EnableWebMvcSecurity will add an AuthenticationPrincipalArgumentResolver. Because that's deprecated, I tried its replacement @EnableWebSecurity but that does not add the resolver.
I worked around this by adding a resolver manually in my Spring MVC configuration, but either @EnableWebSecurity should be updated to do this, or the documentation should be changed.
Rob Winch said:
How are you configuring Spring MVC?
Jon von Kampen said:
Here are my (somewhat redacted) Spring MVC and Spring Security configuration files:
package mypackage.spring.config;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import org.springframework.web.servlet.mvc.WebContentInterceptor;
@Configuration
public class MvcConfig extends WebMvcConfigurationSupport {
@Autowired
private Environment env;
@Override
public void addInterceptors(final InterceptorRegistry registry)
{
registry.addInterceptor(buildWebContentInterceptor());
super.addInterceptors(registry);
}
private HandlerInterceptor buildWebContentInterceptor() {
final WebContentInterceptor interceptor = new WebContentInterceptor();
// Set HTTP headers to instruct clients not to cache
interceptor.setCacheSeconds(0);
interceptor.setUseExpiresHeader(Boolean.TRUE);
interceptor.setUseCacheControlHeader(Boolean.TRUE);
interceptor.setUseCacheControlNoStore(Boolean.TRUE);
return interceptor;
}
@Override
public void addArgumentResolvers(
List<HandlerMethodArgumentResolver> argumentResolvers) {
/* TODO https://jira.spring.io/browse/SEC-2942?filter=-2 */
argumentResolvers.add(new AuthenticationPrincipalArgumentResolver());
}
// Enable method parameter validation annotations
@Bean(name="methodValidationPostProcessor")
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
}
package mypackage.spring.config;
import java.util.Arrays;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import org.springframework.security.web.authentication.preauth.RequestHeaderAuthenticationFilter;
import mypackage.common.security.DynamicUserDetailsService;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private ApplicationContext appContext;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.regexMatcher("/secure(/.*)?")
.addFilter(siteMinderFilter())
.exceptionHandling().accessDeniedPage("/access-denied").and()
.authorizeRequests()
.anyRequest().hasRole("EXAMPLE_ROLE").and()
.csrf().disable();
}
private RequestHeaderAuthenticationFilter siteMinderFilter() {
RequestHeaderAuthenticationFilter filter = new RequestHeaderAuthenticationFilter();
filter.setPrincipalRequestHeader("SMUSER");
filter.setAuthenticationManager(new ProviderManager(
Arrays.asList((AuthenticationProvider) preauthAuthProvider())));
return filter;
}
private PreAuthenticatedAuthenticationProvider preauthAuthProvider() {
PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();
// Custom UserDetailsService
provider.setPreAuthenticatedUserDetailsService(
new UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken>(
new DynamicUserDetailsService()));
return provider;
}
}
Rob Winch said:
Thank your details. I have added a test demoing that this is working for me.
Can you post your controller? Better yet if you can provide a test that demonstrates the problem.
Jon von Kampen said:
I'm wrestling with my Eclipse setup so I can build Spring Sec and write a proper test, but in the meantime, here's a sample controller:
package mypackage.common.security;
import org.springframework.http.MediaType;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/dynamic-user")
public class DynamicUserController {
@RequestMapping(value = "", method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public DynamicUser getUser(
@AuthenticationPrincipal
DynamicUser user) {
return user;
}
}
DynamicUser is a class that extends org.springframework.security.core.userdetails.User with a couple more fields. Even if I use User though, I get the following sort of error when I navigate to the HTTP endpoint:
HTTP ERROR 500
Problem accessing /dynamic-user/. Reason:
Server Error
Caused by:
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [mypackage.common.security.DynamicUser]: No default constructor found; nested exception is java.lang.NoSuchMethodException: mypackage.common.security.DynamicUser.<init>()
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:978)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:857)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:687)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:842)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:790)
at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:808)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1669)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:316)
(etc.)
But when I manually add a AuthenticationPrincipalArgumentResolver to my Spring MVC argument resolvers, it works fine.
Rob Winch said:
jtvonkam I should be more clear on what I mean...
Feel free to just create a project and provide instructions for how to reproduce the issue. It does not even need to be in the form of a unit test. Ideally the project will be easy for me to run (i.e. not require any external setup like a database).
Thanks again!
Jon von Kampen said:
Eclipse Maven demo project for SEC-2942. Run with mvn -P jetty jetty:run. Use a browser extension or proxy to pass an SMUSER HTTP header with any value on each request. Navigating to http://localhost:8080/sec-2942/hello should always return "Hello world!". http://localhost:8080/sec-2942/principal should return JSON of the user principal but raises an exception with the MainConfig.java configuration in use. There are three alternate files that resolve the issue, MainConfig.java.alt1 through 3
Jon von Kampen said:
I attached an Eclipse Maven demo project (sec-2942-demo.zip) with a Spring configuration that reproduces the problem, and three alternate config files that solve the problem. Basically, if my config class extends WebMvcConfigurationSupport, I have to manually add an AuthenticationPrincipalArgumentResolver. If I extend DelegatingWebMvcConfiguration or use @EnableWebMvc, it works.
This might be more of a Spring MVC issue than Spring Security. The javadocs for WebMvcConfigurationSupport and EnableWebMvc led me to believe that using either one without extra bells and whistles was basically equivalent.
WebMvcConfigurationSupport javadoc:
This is the main class providing the configuration behind the MVC Java config. It is typically imported by adding @EnableWebMvc to an application @Configuration class. An alternative more advanced option is to extend directly from this class and override methods as necessary remembering to add @Configuration to the subclass and @Bean to overridden @Bean methods. For more details see the Javadoc of @EnableWebMvc.
EnableWebMvc javadoc:
If WebMvcConfigurer does not expose some advanced setting that needs to be configured, consider removing the @EnableWebMvc annotation and extending directly from WebMvcConfigurationSupport or DelegatingWebMvcConfiguration
Rob Winch said:
Thanks for the detailed report.
This seems like something that could be clarified in Spring Security documentation. In short, Spring Security provides a WebMvcConfigurerAdapter so you must be leveraging DelegatingWebMvcConfiguration (EnableWebMvc will also work since it implicitly uses DelegatingWebMvcConfiguration) for it to work automatically.
I added and fixed SEC-2952 to address the documentation issue.
I'm going to go ahead and resolve the issue. If you believe there is something more that can be done, please feel free to create a new JIRA or reopen this one.
Jon von Kampen said:
Thanks for your help!
Most helpful comment
Rob Winch said:
Thanks for the detailed report.
This seems like something that could be clarified in Spring Security documentation. In short, Spring Security provides a
WebMvcConfigurerAdapterso you must be leveragingDelegatingWebMvcConfiguration(EnableWebMvcwill also work since it implicitly usesDelegatingWebMvcConfiguration) for it to work automatically.I added and fixed SEC-2952 to address the documentation issue.
I'm going to go ahead and resolve the issue. If you believe there is something more that can be done, please feel free to create a new JIRA or reopen this one.