2
votes

I'm moving a Spring MVC servlet 3.1 application over to Spring Boot 1.3.0 and when I add the @EnableGlobalMethodSecurity annotation to one of my Java configuration classes I get an exception on startup (in full below).

I'm subclassing WebSecurityConfigurerAdapter for my spring security support (which has previously worked fine for me)

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityAuthorisationConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(final HttpSecurity http) throws Exception {
    http.csrf()...

Even with no other dependencies @Autowired into the class the startup will fail. Startup will succeed if I remove the @EnableGlobalMethodSecurity annotation, but obviously there's no method security. Most other web security SO posts seem to be due to the security config being unable to start before the dispatcher servlet but with no other dependencies in my security config I can't see why this would be happening.

I've tried quite a number of ways to start the application up and changed the @Order of the security config but to no avail.

My current Application entry point looks like this, though I've also tried initialising with the auto-magical dispatcher and I had the same issue:

@Configuration
@EnableAutoConfiguration
@ComponentScan(basePackages = { "com.myapp.config.web", "com.myapp.config.app" })
public class Application extends SpringBootServletInitializer {
        private static Class<Application> applicationClass = Application.class;

        public static void main(final String[] args) throws Exception {
            SpringApplication.run(applicationClass, args);
        }

        @Autowired
        private AuditLogger auditLogger;

        @Override
        public void onStartup(final ServletContext container) throws ServletException {
            final AnnotationConfigWebApplicationContext rootContext = createRootContext(container);

            setUpSessionConfig(container);

            setUpMdcLoggingFilter(container);

            setAllUndefinedRequestsToUtf8(container);

            setUpAuditLogging(container);

            addSecurityFilter(container);

            addUpdateExpiredPasswordFilter(container);

            createDispatcher(container, rootContext);
        }

        private void setUpSessionConfig(final ServletContext container) {
            container.getSessionCookieConfig().setHttpOnly(true);
            container.setSessionTrackingModes(asSet(SessionTrackingMode.COOKIE));
        }

        private void setUpMdcLoggingFilter(final ServletContext container) {
            final Dynamic mdcFilter = container.addFilter("mdcInsertingFilter",
                    new DelegatingFilterProxy(mdcInsertingFilter()));
            mdcFilter.addMappingForUrlPatterns(null, true, "/*");
        }

        private void addUpdateExpiredPasswordFilter(final ServletContext container) {
            final Dynamic filter = container.addFilter("updateExpiredPasswordFilter",
                    new DelegatingFilterProxy(updateExpiredPasswordFilter()));
            filter.addMappingForUrlPatterns(null, true, "/*");
        }

        private void setUpAuditLogging(final ServletContext container) {
            final Dynamic auditLoggingFilter = container.addFilter("auditLogFilter",
                    new DelegatingFilterProxy(auditLogFilter()));
            auditLoggingFilter.addMappingForUrlPatterns(null, true, "/*");

        }

        private void addSecurityFilter(final ServletContext container) {
            final Dynamic securityFilter =
                    container.addFilter(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME,
                            DelegatingFilterProxy.class);
            securityFilter.addMappingForUrlPatterns(null, true, "/*");
        }

        private AnnotationConfigWebApplicationContext createRootContext(final ServletContext servletContext) {
            final AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
            rootContext.register(AppConfig.class);
            rootContext.setServletContext(servletContext);

            servletContext.addListener(new ContextLoaderListener(rootContext));
            servletContext.setInitParameter("spring.profiles.default", "production");
            return rootContext;
        }

        private void setAllUndefinedRequestsToUtf8(final ServletContext container) {
            final FilterRegistration filter = container.addFilter("encodingFilter", OrderedCharacterEncodingFilter.class);
            filter.setInitParameter("encoding", "UTF-8");
            filter.addMappingForUrlPatterns(null, true, "/*");
        }

        private void createDispatcher(final ServletContext container,
                final AnnotationConfigWebApplicationContext rootContext) {
            final DispatcherServlet dispatcherServlet = new DispatcherServlet(rootContext);
            final ServletRegistration.Dynamic dispatcher =
                    container.addServlet("myServlet", dispatcherServlet);

            dispatcher.setLoadOnStartup(1);
            dispatcher.addMapping("/");
        }

        @Bean
        public AuditLogFilter auditLogFilter() {
            final AuditLogFilter auditLogFilter = new AuditLogFilter();
            auditLogFilter.setAuditLogger(auditLogger);
            auditLogFilter.setIgnoredPaths("/resources,/webjars");
            return auditLogFilter;
        }

        @Bean
        public UpdateExpiredPasswordFilter updateExpiredPasswordFilter() {
            final UpdateExpiredPasswordFilter filter = new UpdateExpiredPasswordFilter();
            filter.setPasswordUpdateFormPath("/user/change_password");
            filter.setPasswordUpdatePath("user/change_password");
            filter.setLogoutPath("/logout");
            filter.setIgnoredPaths("/resources,/webjars");
            return filter;
        }

        @Bean(name = "mdcInsertingFilter")
        public MDCInsertingServletFilter mdcInsertingFilter() {
            return new MDCInsertingServletFilter();
        }
}

While I'm not necessarily expecting to receive a comprehensive solution to this it would be very useful to know where to start looking for the cause of the problem. Is it a bug? Could it be to do with the way I'm creating the application?

Stack Trace:

2015-11-20 17:18:02.337 ERROR 80806 --- [ main] o.s.boot.SpringApplication : Application startup failed org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'defaultServletHandlerMapping' defined in class path resource [org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.web.servlet.HandlerMapping]: Factory method 'defaultServletHandlerMapping' threw exception; nested exception is java.lang.IllegalArgumentException: A ServletContext is required to configure default servlet handling at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:599) ~[spring-beans-4.2.3.RELEASE.jar:4.2.3.RELEASE] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1123) ~[spring-beans-4.2.3.RELEASE.jar:4.2.3.RELEASE] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1018) ~[spring-beans-4.2.3.RELEASE.jar:4.2.3.RELEASE] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:510) ~[spring-beans-4.2.3.RELEASE.jar:4.2.3.RELEASE] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:482) ~[spring-beans-4.2.3.RELEASE.jar:4.2.3.RELEASE] at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306) ~[spring-beans-4.2.3.RELEASE.jar:4.2.3.RELEASE] at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230) ~[spring-beans-4.2.3.RELEASE.jar:4.2.3.RELEASE] at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302) ~[spring-beans-4.2.3.RELEASE.jar:4.2.3.RELEASE] at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197) ~[spring-beans-4.2.3.RELEASE.jar:4.2.3.RELEASE] at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:772) ~[spring-beans-4.2.3.RELEASE.jar:4.2.3.RELEASE] at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:838) ~[spring-context-4.2.3.RELEASE.jar:4.2.3.RELEASE] at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:537) ~[spring-context-4.2.3.RELEASE.jar:4.2.3.RELEASE] at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:118) ~[spring-boot-1.3.0.RELEASE.jar:1.3.0.RELEASE] at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:752) [spring-boot-1.3.0.RELEASE.jar:1.3.0.RELEASE] at org.springframework.boot.SpringApplication.doRun(SpringApplication.java:347) [spring-boot-1.3.0.RELEASE.jar:1.3.0.RELEASE] at org.springframework.boot.SpringApplication.run(SpringApplication.java:295) [spring-boot-1.3.0.RELEASE.jar:1.3.0.RELEASE] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1112) [spring-boot-1.3.0.RELEASE.jar:1.3.0.RELEASE] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1101) [spring-boot-1.3.0.RELEASE.jar:1.3.0.RELEASE] at com.amberhill.web.Application.main(Application.java:51) [bin/:na] Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.web.servlet.HandlerMapping]: Factory method 'defaultServletHandlerMapping' threw exception; nested exception is java.lang.IllegalArgumentException: A ServletContext is required to configure default servlet handling at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:189) ~[spring-beans-4.2.3.RELEASE.jar:4.2.3.RELEASE] at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:588) ~[spring-beans-4.2.3.RELEASE.jar:4.2.3.RELEASE] ... 18 common frames omitted Caused by: java.lang.IllegalArgumentException: A ServletContext is required to configure default servlet handling at org.springframework.util.Assert.notNull(Assert.java:115) ~[spring-core-4.2.3.RELEASE.jar:4.2.3.RELEASE] at org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer.(DefaultServletHandlerConfigurer.java:53) ~[spring-webmvc-4.2.3.RELEASE.jar:4.2.3.RELEASE] at org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport.defaultServletHandlerMapping(WebMvcConfigurationSupport.java:450) ~[spring-webmvc-4.2.3.RELEASE.jar:4.2.3.RELEASE] at org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration$EnableWebMvcConfiguration$$EnhancerBySpringCGLIB$$5204f0e6.CGLIB$defaultServletHandlerMapping$34() ~[spring-boot-autoconfigure-1.3.0.RELEASE.jar:1.3.0.RELEASE] at org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration$EnableWebMvcConfiguration$$EnhancerBySpringCGLIB$$5204f0e6$$FastClassBySpringCGLIB$$7ec661f1.invoke() ~[spring-boot-autoconfigure-1.3.0.RELEASE.jar:1.3.0.RELEASE] at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:228) ~[spring-core-4.2.3.RELEASE.jar:4.2.3.RELEASE] at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:318) ~[spring-context-4.2.3.RELEASE.jar:4.2.3.RELEASE] at org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration$EnableWebMvcConfiguration$$EnhancerBySpringCGLIB$$5204f0e6.defaultServletHandlerMapping() ~[spring-boot-autoconfigure-1.3.0.RELEASE.jar:1.3.0.RELEASE] at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_11] at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_11] at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_11] at java.lang.reflect.Method.invoke(Method.java:483) ~[na:1.8.0_11] at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:162) ~[spring-beans-4.2.3.RELEASE.jar:4.2.3.RELEASE] ... 19 common frames omitted

1

1 Answers

2
votes

I finally managed to get this working by moving the @EnableGlobalMethodSecurity(prePostEnabled = true) annotation from the WebSecurityConfigurerAdapter implementation to it's own config class and extending GlobalMethodSecurityConfiguration to configure an authentication manager.

I have a feeling this is a work-around fix but at least it's fixed.

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
    @Override
    public void configure(final AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(authenticationProvider());
    }

    @Bean
    public DaoAuthenticationProvider authenticationProvider() {
        final DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
        authProvider.setPasswordEncoder(new StandardPasswordEncoder());
        authProvider.setUserDetailsService(userDetailsService());
        return authProvider;
    }

    @Bean
    public MyUserDetailsService userDetailsService() {
        return new MyUserDetailsService(...);
    }

}