Sfoglia il codice sorgente

SEC-2447: Fix AuthenticationManagerBuilder ordering issues

Rob Winch 11 anni fa
parent
commit
6c35c33abe
21 ha cambiato i file con 994 aggiunte e 68 eliminazioni
  1. 6 2
      config/config.gradle
  2. 22 1
      config/src/main/java/org/springframework/security/config/annotation/authentication/builders/AuthenticationManagerBuilder.java
  3. 105 1
      config/src/main/java/org/springframework/security/config/annotation/authentication/configuration/AuthenticationConfiguration.java
  4. 91 0
      config/src/main/java/org/springframework/security/config/annotation/authentication/configuration/EnableGlobalAuthentication.java
  5. 39 0
      config/src/main/java/org/springframework/security/config/annotation/authentication/configurers/GlobalAuthenticationConfigurerAdapter.java
  6. 3 2
      config/src/main/java/org/springframework/security/config/annotation/method/configuration/EnableGlobalMethodSecurity.java
  7. 14 35
      config/src/main/java/org/springframework/security/config/annotation/method/configuration/GlobalMethodSecurityConfiguration.java
  8. 3 2
      config/src/main/java/org/springframework/security/config/annotation/web/configuration/EnableWebSecurity.java
  9. 12 16
      config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurerAdapter.java
  10. 2 0
      config/src/main/java/org/springframework/security/config/annotation/web/servlet/configuration/EnableWebMvcSecurity.java
  11. 1 1
      config/src/main/java/org/springframework/security/config/annotation/web/servlet/configuration/WebMvcSecurityConfiguration.java
  12. 33 0
      config/src/test/groovy/org/springframework/security/config/annotation/authentication/AuthenticationManagerBuilderTests.groovy
  13. 295 0
      config/src/test/groovy/org/springframework/security/config/annotation/authentication/configuration/AuthenticationConfigurationTests.groovy
  14. 68 0
      config/src/test/groovy/org/springframework/security/config/annotation/issue50/ApplicationConfig.java
  15. 100 0
      config/src/test/groovy/org/springframework/security/config/annotation/issue50/Issue50Tests.groovy
  16. 90 0
      config/src/test/groovy/org/springframework/security/config/annotation/issue50/SecurityConfig.java
  17. 62 0
      config/src/test/groovy/org/springframework/security/config/annotation/issue50/domain/User.java
  18. 30 0
      config/src/test/groovy/org/springframework/security/config/annotation/issue50/repo/UserRepository.java
  19. 14 8
      config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/Issue55Tests.groovy
  20. 2 0
      docs/guides/src/asciidoc/_hello-includes/secure-the-application.asc
  21. 2 0
      docs/manual/src/asciidoc/index.adoc

+ 6 - 2
config/config.gradle

@@ -35,6 +35,7 @@ dependencies {
                 "org.springframework:spring-orm:$springVersion",
                 "org.springframework:spring-tx:$springVersion",
                 "org.spockframework:spock-core:$spockVersion",
+                "org.spockframework:spock-spring:$spockVersion",
                 "org.slf4j:jcl-over-slf4j:$slf4jVersion",
                 "org.hibernate.javax.persistence:hibernate-jpa-2.0-api:1.0.1.Final",
                 "org.hibernate:hibernate-entitymanager:4.1.0.Final",
@@ -46,12 +47,15 @@ dependencies {
                 "org.apache.directory.server:apacheds-server-jndi:$apacheDsVersion",
                 'org.apache.directory.shared:shared-ldap:0.9.15',
                 'ldapsdk:ldapsdk:4.1',
-                powerMockDependencies
+                powerMockDependencies,
+                "org.springframework.data:spring-data-jpa:1.2.0.RELEASE",
+                "org.hibernate.javax.persistence:hibernate-jpa-2.0-api:1.0.0.Final",
+                 "org.hibernate:hibernate-entitymanager:3.6.10.Final",
+                "org.hsqldb:hsqldb:2.2.8"
     testCompile('org.openid4java:openid4java-nodeps:0.9.6') {
        exclude group: 'com.google.code.guice', module: 'guice'
     }
 
-
     testRuntime "org.hsqldb:hsqldb:$hsqlVersion",
                 "cglib:cglib-nodep:2.2"
 }

+ 22 - 1
config/src/main/java/org/springframework/security/config/annotation/authentication/builders/AuthenticationManagerBuilder.java

@@ -27,6 +27,7 @@ import org.springframework.security.authentication.ProviderManager;
 import org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder;
 import org.springframework.security.config.annotation.ObjectPostProcessor;
 import org.springframework.security.config.annotation.SecurityBuilder;
+import org.springframework.security.config.annotation.SecurityConfigurer;
 import org.springframework.security.config.annotation.authentication.ProviderManagerBuilder;
 import org.springframework.security.config.annotation.authentication.configurers.ldap.LdapAuthenticationProviderConfigurer;
 import org.springframework.security.config.annotation.authentication.configurers.provisioning.InMemoryUserDetailsManagerConfigurer;
@@ -221,7 +222,7 @@ public class AuthenticationManagerBuilder extends AbstractConfiguredSecurityBuil
 
     @Override
     protected ProviderManager performBuild() throws Exception {
-        if(authenticationProviders.isEmpty() && parentAuthenticationManager == null) {
+        if(!isConfigured()) {
             logger.debug("No authenticationProviders and no parentAuthenticationManager defined. Returning null.");
             return null;
         }
@@ -236,6 +237,26 @@ public class AuthenticationManagerBuilder extends AbstractConfiguredSecurityBuil
         return providerManager;
     }
 
+    /**
+     * Determines if the {@link AuthenticationManagerBuilder} is configured to
+     * build a non null {@link AuthenticationManager}. This means that either a
+     * non-null parent is specified or at least one
+     * {@link AuthenticationProvider} has been specified.
+     *
+     * <p>
+     * When using {@link SecurityConfigurer} instances, the
+     * {@link AuthenticationManagerBuilder} will not be configured until the
+     * {@link SecurityConfigurer#configure(SecurityBuilder)} methods. This means
+     * a {@link SecurityConfigurer} that is last could check this method and
+     * provide a default configuration in the
+     * {@link SecurityConfigurer#configure(SecurityBuilder)} method.
+     *
+     * @return
+     */
+    public boolean isConfigured() {
+        return !authenticationProviders.isEmpty() || parentAuthenticationManager != null;
+    }
+
     /**
      * Gets the default {@link UserDetailsService} for the
      * {@link AuthenticationManagerBuilder}. The result may be null in some

+ 105 - 1
config/src/main/java/org/springframework/security/config/annotation/authentication/configuration/AuthenticationConfiguration.java

@@ -15,10 +15,25 @@
  */
 package org.springframework.security.config.annotation.authentication.configuration;
 
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.aop.framework.ProxyFactoryBean;
+import org.springframework.aop.target.LazyInitTargetSource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
+import org.springframework.core.annotation.AnnotationAwareOrderComparator;
+import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.config.annotation.ObjectPostProcessor;
 import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.authentication.configurers.GlobalAuthenticationConfigurerAdapter;
+import org.springframework.util.Assert;
 
 /**
  * Exports the authentication {@link Configuration}
@@ -29,9 +44,98 @@ import org.springframework.security.config.annotation.authentication.builders.Au
  */
 @Configuration
 public class AuthenticationConfiguration {
+    private ApplicationContext applicationContext;
+
+    private AuthenticationManager authenticationManager;
+
+    private boolean authenticationManagerInitialized;
+
+    private List<GlobalAuthenticationConfigurerAdapter> globalAuthConfigures = Collections.emptyList();
+
+    private ObjectPostProcessor<Object> objectPostProcessor;
 
     @Bean
     public AuthenticationManagerBuilder authenticationManagerBuilder(ObjectPostProcessor<Object> objectPostProcessor) {
         return new AuthenticationManagerBuilder(objectPostProcessor);
     }
-}
+
+    @Bean
+    public GlobalAuthenticationConfigurerAdapter enableGlobalAuthenticationAutowiredConfigurer(ApplicationContext context) {
+        return new EnableGlobalAuthenticationAutowiredConfigurer(context);
+    }
+
+    public AuthenticationManager getAuthenticationManager() throws Exception {
+        if(authenticationManagerInitialized) {
+            return authenticationManager;
+        }
+
+        AuthenticationManagerBuilder authBuilder = authenticationManagerBuilder(objectPostProcessor);
+        for(GlobalAuthenticationConfigurerAdapter config : globalAuthConfigures) {
+            authBuilder.apply(config);
+        }
+
+        authenticationManager = authBuilder.build();
+
+        if(authenticationManager == null) {
+            authenticationManager = getAuthenticationMangerBean();
+        }
+
+        this.authenticationManagerInitialized = true;
+        return authenticationManager;
+    }
+
+    @Autowired(required = false)
+    public void setGlobalAuthenticationConfigurers(List<GlobalAuthenticationConfigurerAdapter> configurers) throws Exception {
+        Collections.sort(configurers, AnnotationAwareOrderComparator.INSTANCE);
+        this.globalAuthConfigures = configurers;
+    }
+
+    @Autowired
+    public void setApplicationContext(ApplicationContext applicationContext) {
+        this.applicationContext = applicationContext;
+    }
+
+    @Autowired
+    public void setObjectPostProcessor(ObjectPostProcessor<Object> objectPostProcessor) {
+        this.objectPostProcessor = objectPostProcessor;
+    }
+
+
+    @SuppressWarnings("unchecked")
+    private <T> T lazyBean(Class<T> interfaceName) {
+        LazyInitTargetSource lazyTargetSource = new LazyInitTargetSource();
+        String[] beanNamesForType = applicationContext.getBeanNamesForType(interfaceName);
+        if(beanNamesForType.length == 0) {
+            return null;
+        }
+        Assert.isTrue(beanNamesForType.length == 1 , "Expecting to only find a single bean for type " + interfaceName + ", but found " + Arrays.asList(beanNamesForType));
+        lazyTargetSource.setTargetBeanName(beanNamesForType[0]);
+        lazyTargetSource.setBeanFactory(applicationContext);
+        ProxyFactoryBean proxyFactory = new ProxyFactoryBean();
+        proxyFactory.setTargetSource(lazyTargetSource);
+        proxyFactory.setInterfaces(new Class[] { interfaceName });
+        return (T) proxyFactory.getObject();
+    }
+
+    private AuthenticationManager getAuthenticationMangerBean() {
+        return lazyBean(AuthenticationManager.class);
+    }
+
+
+    private static class EnableGlobalAuthenticationAutowiredConfigurer extends GlobalAuthenticationConfigurerAdapter {
+        private final ApplicationContext context;
+        private static final Log logger = LogFactory.getLog(EnableGlobalAuthenticationAutowiredConfigurer.class);
+
+        public EnableGlobalAuthenticationAutowiredConfigurer(ApplicationContext context) {
+            this.context = context;
+        }
+
+        @Override
+        public void init(AuthenticationManagerBuilder auth) {
+            Map<String, Object> beansWithAnnotation = context.getBeansWithAnnotation(EnableGlobalAuthentication.class);
+            if(logger.isDebugEnabled()) {
+                logger.debug("Eagerly initializing " + beansWithAnnotation);
+            }
+        }
+    }
+}

+ 91 - 0
config/src/main/java/org/springframework/security/config/annotation/authentication/configuration/EnableGlobalAuthentication.java

@@ -0,0 +1,91 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.security.config.annotation.authentication.configuration;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import org.springframework.context.annotation.Import;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity;
+
+/**
+ * The {@link EnableGlobalAuthentication} annotation signals that the annotated
+ * class can be used to configure a global instance of
+ * {@link AuthenticationManagerBuilder}. For example:
+ *
+ * <pre class="code">
+ * &#064;Configuration
+ * &#064;EnableGlobalAuthentication
+ * public class MyGlobalAuthenticationConfiguration {
+ *
+ *     &#064;Autowired
+ *     public void configureGlobal(AuthenticationManagerBuilder auth) {
+ *        auth
+ *            .inMemoryAuthentication()
+ *                .withUser(&quot;user&quot;).password(&quot;password&quot;).roles(&quot;USER&quot;).and()
+ *                .withUser(&quot;admin&quot;).password(&quot;password&quot;).roles(&quot;USER&quot;, &quot;ADMIN&quot;);
+ *     }
+ * }
+ * </pre>
+ *
+ * Annotations that are annotated with {@link EnableGlobalAuthentication} also
+ * signal that the annotated class can be used to configure a global instance of
+ * {@link AuthenticationManagerBuilder}. For example:
+ *
+ * <pre class="code">
+ * &#064;Configuration
+ * &#064;EnableWebSecurity
+ * public class MyWebSecurityConfiguration extends WebSecurityConfigurerAdapter {
+ *
+ *    &#064;Autowired
+ *    public void configureGlobal(AuthenticationManagerBuilder auth) {
+ *        auth
+ *            .inMemoryAuthentication()
+ *                .withUser(&quot;user&quot;).password(&quot;password&quot;).roles(&quot;USER&quot;).and()
+ *                .withUser(&quot;admin&quot;).password(&quot;password&quot;).roles(&quot;USER&quot;, &quot;ADMIN&quot;);
+ *    }
+ *
+ *    // Possibly overridden methods ...
+ * }
+ * </pre>
+ *
+ * The following annotations are annotated with {@link EnableGlobalAuthentication}
+ *
+ * <ul>
+ *   <li> {@link EnableWebSecurity} </li>
+ *   <li> {@link EnableWebMvcSecurity} </li>
+ *   <li> {@link EnableGlobalMethodSecurity} </li>
+ * </ul>
+ *
+ * Configuring {@link AuthenticationManagerBuilder} in a class without the {@link EnableGlobalAuthentication} annotation has
+ * unpredictable results.
+ *
+ * @see EnableWebMvcSecurity
+ * @see EnableWebSecurity
+ * @see EnableGlobalMethodSecurity
+ *
+ * @author Rob Winch
+ *
+ */
+@Retention(value=java.lang.annotation.RetentionPolicy.RUNTIME)
+@Target(value={java.lang.annotation.ElementType.TYPE})
+@Documented
+@Import(AuthenticationConfiguration.class)
+public @interface EnableGlobalAuthentication {}

+ 39 - 0
config/src/main/java/org/springframework/security/config/annotation/authentication/configurers/GlobalAuthenticationConfigurerAdapter.java

@@ -0,0 +1,39 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.security.config.annotation.authentication.configurers;
+
+import org.springframework.core.annotation.Order;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.config.annotation.SecurityConfigurer;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
+
+/**
+ * A {@link SecurityConfigurer} that can be exposed as a bean to configure the
+ * global {@link AuthenticationManagerBuilder}. Beans of this type are
+ * automatically used by {@link AuthenticationConfiguration} to configure the
+ * global {@link AuthenticationManagerBuilder}.
+ *
+ * @since 3.2.1
+ * @author Rob Winch
+ */
+@Order(100)
+public abstract class GlobalAuthenticationConfigurerAdapter implements SecurityConfigurer<AuthenticationManager, AuthenticationManagerBuilder> {
+
+    public void init(AuthenticationManagerBuilder auth) throws Exception {}
+
+    public void configure(AuthenticationManagerBuilder auth) throws Exception {}
+}

+ 3 - 2
config/src/main/java/org/springframework/security/config/annotation/method/configuration/EnableGlobalMethodSecurity.java

@@ -23,7 +23,7 @@ import org.springframework.context.annotation.AdviceMode;
 import org.springframework.context.annotation.Import;
 import org.springframework.core.Ordered;
 import org.springframework.security.access.annotation.Secured;
-import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
+import org.springframework.security.config.annotation.authentication.configuration.EnableGlobalAuthentication;
 import org.springframework.security.config.annotation.configuration.ObjectPostProcessorConfiguration;
 
 /**
@@ -44,7 +44,8 @@ import org.springframework.security.config.annotation.configuration.ObjectPostPr
 @Retention(value=java.lang.annotation.RetentionPolicy.RUNTIME)
 @Target(value={java.lang.annotation.ElementType.TYPE})
 @Documented
-@Import({GlobalMethodSecuritySelector.class,ObjectPostProcessorConfiguration.class,AuthenticationConfiguration.class})
+@Import({GlobalMethodSecuritySelector.class,ObjectPostProcessorConfiguration.class})
+@EnableGlobalAuthentication
 public @interface EnableGlobalMethodSecurity {
 
     /**

+ 14 - 35
config/src/main/java/org/springframework/security/config/annotation/method/configuration/GlobalMethodSecurityConfiguration.java

@@ -16,19 +16,14 @@
 package org.springframework.security.config.annotation.method.configuration;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
 
 import org.aopalliance.intercept.MethodInterceptor;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
-import org.springframework.aop.framework.ProxyFactoryBean;
-import org.springframework.aop.target.LazyInitTargetSource;
-import org.springframework.beans.factory.NoSuchBeanDefinitionException;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.config.BeanDefinition;
-import org.springframework.context.ApplicationContext;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.ImportAware;
@@ -67,6 +62,7 @@ import org.springframework.security.authentication.AuthenticationTrustResolver;
 import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
 import org.springframework.security.config.annotation.ObjectPostProcessor;
 import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
 import org.springframework.util.Assert;
 
 /**
@@ -81,7 +77,6 @@ import org.springframework.util.Assert;
 @Configuration
 public class GlobalMethodSecurityConfiguration implements ImportAware {
     private static final Log logger = LogFactory.getLog(GlobalMethodSecurityConfiguration.class);
-    private ApplicationContext context;
     private ObjectPostProcessor<Object> objectPostProcessor = new ObjectPostProcessor<Object>() {
         public <T> T postProcess(T object) {
             throw new IllegalStateException(ObjectPostProcessor.class.getName()+ " is a required bean. Ensure you have used @"+EnableGlobalMethodSecurity.class.getName());
@@ -93,6 +88,7 @@ public class GlobalMethodSecurityConfiguration implements ImportAware {
     private boolean disableAuthenticationRegistry;
     private AnnotationAttributes enableMethodSecurity;
     private MethodSecurityExpressionHandler expressionHandler;
+    private AuthenticationConfiguration authenticationConfiguration;
 
     /**
      * Creates the default MethodInterceptor which is a MethodSecurityInterceptor using the following methods to
@@ -248,19 +244,11 @@ public class GlobalMethodSecurityConfiguration implements ImportAware {
             auth = new AuthenticationManagerBuilder(objectPostProcessor);
             auth.authenticationEventPublisher(eventPublisher);
             configure(auth);
-            if(!disableAuthenticationRegistry) {
+            if(disableAuthenticationRegistry) {
+                authenticationManager = getAuthenticationConfiguration().getAuthenticationManager();
+            } else {
                 authenticationManager = auth.build();
             }
-            if(authenticationManager == null) {
-                try {
-                    authenticationManager = context.getBean(AuthenticationManagerBuilder.class).getOrBuild();
-                } catch(NoSuchBeanDefinitionException e) {
-                    logger.debug("Could not obtain the AuthenticationManagerBuilder. This is ok for now, we will try using an AuthenticationManager directly",e);
-                }
-            }
-            if(authenticationManager == null) {
-                authenticationManager = lazyBean(AuthenticationManager.class);
-            }
         }
         return authenticationManager;
     }
@@ -351,11 +339,6 @@ public class GlobalMethodSecurityConfiguration implements ImportAware {
         this.defaultMethodExpressionHandler.setTrustResolver(trustResolver);
     }
 
-    @Autowired
-    public void setApplicationContext(ApplicationContext context) {
-        this.context = context;
-    }
-
     @Autowired(required=false)
     public void setObjectPostProcessor(ObjectPostProcessor<Object> objectPostProcessor) {
         this.objectPostProcessor = objectPostProcessor;
@@ -370,17 +353,14 @@ public class GlobalMethodSecurityConfiguration implements ImportAware {
         this.defaultMethodExpressionHandler.setPermissionEvaluator(permissionEvaluators.get(0));
     }
 
-    @SuppressWarnings("unchecked")
-    private <T> T lazyBean(Class<T> interfaceName) {
-        LazyInitTargetSource lazyTargetSource = new LazyInitTargetSource();
-        String[] beanNamesForType = context.getBeanNamesForType(interfaceName);
-        Assert.isTrue(beanNamesForType.length == 1 , "Expecting to only find a single bean for type " + interfaceName + ", but found " + Arrays.asList(beanNamesForType));
-        lazyTargetSource.setTargetBeanName(beanNamesForType[0]);
-        lazyTargetSource.setBeanFactory(context);
-        ProxyFactoryBean proxyFactory = new ProxyFactoryBean();
-        proxyFactory.setTargetSource(lazyTargetSource);
-        proxyFactory.setInterfaces(new Class[] { interfaceName });
-        return (T) proxyFactory.getObject();
+    @Autowired(required = false)
+    public void setAuthenticationConfiguration(AuthenticationConfiguration authenticationConfiguration) {
+        this.authenticationConfiguration = authenticationConfiguration;
+    }
+
+    private AuthenticationConfiguration getAuthenticationConfiguration() {
+        Assert.notNull(authenticationConfiguration, "authenticationConfiguration cannot be null");
+        return authenticationConfiguration;
     }
 
     private boolean prePostEnabled() {
@@ -414,5 +394,4 @@ public class GlobalMethodSecurityConfiguration implements ImportAware {
         }
         return this.enableMethodSecurity;
     }
-
-}
+}

+ 3 - 2
config/src/main/java/org/springframework/security/config/annotation/web/configuration/EnableWebSecurity.java

@@ -20,7 +20,7 @@ import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
 
 import org.springframework.context.annotation.Import;
-import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
+import org.springframework.security.config.annotation.authentication.configuration.EnableGlobalAuthentication;
 import org.springframework.security.config.annotation.configuration.ObjectPostProcessorConfiguration;
 import org.springframework.security.config.annotation.web.WebSecurityConfigurer;
 
@@ -77,7 +77,8 @@ import org.springframework.security.config.annotation.web.WebSecurityConfigurer;
 @Retention(value=java.lang.annotation.RetentionPolicy.RUNTIME)
 @Target(value={java.lang.annotation.ElementType.TYPE})
 @Documented
-@Import({WebSecurityConfiguration.class,ObjectPostProcessorConfiguration.class,AuthenticationConfiguration.class})
+@Import({WebSecurityConfiguration.class,ObjectPostProcessorConfiguration.class})
+@EnableGlobalAuthentication
 public @interface EnableWebSecurity {
 
     /**

+ 12 - 16
config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurerAdapter.java

@@ -21,7 +21,6 @@ import java.util.List;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
-import org.springframework.beans.factory.NoSuchBeanDefinitionException;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.ApplicationContext;
 import org.springframework.core.annotation.Order;
@@ -31,6 +30,7 @@ import org.springframework.security.authentication.AuthenticationTrustResolverIm
 import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
 import org.springframework.security.config.annotation.ObjectPostProcessor;
 import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
 import org.springframework.security.config.annotation.web.WebSecurityConfigurer;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.builders.WebSecurity;
@@ -43,6 +43,7 @@ import org.springframework.security.core.userdetails.UserDetailsService;
 import org.springframework.security.core.userdetails.UsernameNotFoundException;
 import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
 import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter;
+import org.springframework.util.Assert;
 import org.springframework.web.accept.ContentNegotiationStrategy;
 import org.springframework.web.accept.HeaderContentNegotiationStrategy;
 
@@ -68,6 +69,7 @@ public abstract class WebSecurityConfigurerAdapter implements WebSecurityConfigu
         }
     };
 
+    private AuthenticationConfiguration authenticationConfiguration;
     private AuthenticationManagerBuilder authenticationBuilder;
     private AuthenticationManagerBuilder parentAuthenticationBuilder;
     private boolean disableAuthenticationRegistration;
@@ -221,18 +223,7 @@ public abstract class WebSecurityConfigurerAdapter implements WebSecurityConfigu
         if(!authenticationManagerInitialized) {
             configure(parentAuthenticationBuilder);
             if(disableAuthenticationRegistration) {
-                try {
-                    authenticationManager = context.getBean(AuthenticationManagerBuilder.class).getOrBuild();
-                } catch(NoSuchBeanDefinitionException e) {
-                    logger.debug("Could not obtain the AuthenticationManagerBuilder. This is ok for now, we will try using an AuthenticationManager directly",e);
-                }
-                if(authenticationManager == null) {
-                    try {
-                        authenticationManager = context.getBean(AuthenticationManager.class);
-                    } catch(NoSuchBeanDefinitionException e) {
-                        logger.debug("The AuthenticationManager was not found. This is ok for now as it may not be required.",e);
-                    }
-                }
+                authenticationManager = authenticationConfiguration.getAuthenticationManager();
             } else {
                 authenticationManager = parentAuthenticationBuilder.build();
             }
@@ -341,7 +332,7 @@ public abstract class WebSecurityConfigurerAdapter implements WebSecurityConfigu
         this.contentNegotiationStrategy = contentNegotiationStrategy;
     }
 
-    @Autowired(required=false)
+    @Autowired
     public void setObjectPostProcessor(ObjectPostProcessor<Object> objectPostProcessor) {
         this.objectPostProcessor = objectPostProcessor;
 
@@ -356,6 +347,10 @@ public abstract class WebSecurityConfigurerAdapter implements WebSecurityConfigu
         };
     }
 
+    @Autowired
+    public void setAuthenticationConfiguration(AuthenticationConfiguration authenticationConfiguration) {
+        this.authenticationConfiguration = authenticationConfiguration;
+    }
 
     /**
      * Delays the use of the {@link UserDetailsService} from the
@@ -416,8 +411,9 @@ public abstract class WebSecurityConfigurerAdapter implements WebSecurityConfigu
         private AuthenticationManager delegate;
         private final Object delegateMonitor = new Object();
 
-        AuthenticationManagerDelegator(AuthenticationManagerBuilder authentication) {
-            this.delegateBuilder = authentication;
+        AuthenticationManagerDelegator(AuthenticationManagerBuilder delegateBuilder) {
+            Assert.notNull(delegateBuilder,"delegateBuilder cannot be null");
+            this.delegateBuilder = delegateBuilder;
         }
 
         public Authentication authenticate(Authentication authentication) throws AuthenticationException {

+ 2 - 0
config/src/main/java/org/springframework/security/config/annotation/web/servlet/configuration/EnableWebMvcSecurity.java

@@ -20,6 +20,7 @@ import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
 
 import org.springframework.context.annotation.Import;
+import org.springframework.security.config.annotation.authentication.configuration.EnableGlobalAuthentication;
 
 
 /**
@@ -33,5 +34,6 @@ import org.springframework.context.annotation.Import;
 @Target(value={java.lang.annotation.ElementType.TYPE})
 @Documented
 @Import(WebMvcSecurityConfiguration.class)
+@EnableGlobalAuthentication
 public @interface EnableWebMvcSecurity {
 }

+ 1 - 1
config/src/main/java/org/springframework/security/config/annotation/web/servlet/configuration/WebMvcSecurityConfiguration.java

@@ -52,4 +52,4 @@ public class WebMvcSecurityConfiguration extends WebMvcConfigurerAdapter {
     public RequestDataValueProcessor requestDataValueProcessor() {
         return new CsrfRequestDataValueProcessor();
     }
-}
+}

+ 33 - 0
config/src/test/groovy/org/springframework/security/config/annotation/authentication/AuthenticationManagerBuilderTests.groovy

@@ -87,4 +87,37 @@ class AuthenticationManagerBuilderTests extends BaseSpringSpec {
                     .withUser("admin").password("password").roles("USER","ADMIN")
         }
     }
+
+    def "isConfigured with AuthenticationProvider"() {
+        setup:
+            ObjectPostProcessor opp = Mock()
+            AuthenticationProvider provider = Mock()
+            AuthenticationManagerBuilder auth = new AuthenticationManagerBuilder(opp)
+        when:
+            auth
+                .authenticationProvider(provider)
+        then:
+            auth.isConfigured()
+    }
+
+    def "isConfigured with parent"() {
+        setup:
+            ObjectPostProcessor opp = Mock()
+            AuthenticationManager parent = Mock()
+            AuthenticationManagerBuilder auth = new AuthenticationManagerBuilder(opp)
+        when:
+            auth
+                .parentAuthenticationManager(parent)
+        then:
+            auth.isConfigured()
+    }
+
+    def "isConfigured not configured"() {
+        setup:
+            ObjectPostProcessor opp = Mock()
+        when:
+            AuthenticationManagerBuilder auth = new AuthenticationManagerBuilder(opp)
+        then:
+            auth.isConfigured() == false
+    }
 }

+ 295 - 0
config/src/test/groovy/org/springframework/security/config/annotation/authentication/configuration/AuthenticationConfigurationTests.groovy

@@ -0,0 +1,295 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.security.config.annotation.authentication.configuration;
+
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.context.annotation.Import
+import org.springframework.core.Ordered
+import org.springframework.core.annotation.Order
+import org.springframework.security.access.annotation.Secured
+import org.springframework.security.authentication.AuthenticationManager
+import org.springframework.security.authentication.TestingAuthenticationToken
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
+import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
+import org.springframework.security.config.annotation.BaseSpringSpec
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
+import org.springframework.security.config.annotation.authentication.configurers.GlobalAuthenticationConfigurerAdapter
+import org.springframework.security.config.annotation.configuration.ObjectPostProcessorConfiguration
+import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
+import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity
+import org.springframework.security.core.AuthenticationException
+import org.springframework.security.core.authority.AuthorityUtils
+import org.springframework.security.core.context.SecurityContextHolder
+import org.springframework.security.core.userdetails.User
+import org.springframework.security.provisioning.InMemoryUserDetailsManager
+
+class AuthenticationConfigurationTests extends BaseSpringSpec {
+
+    def "Ordering Autowired on EnableGlobalMethodSecurity"() {
+        setup:
+            SecurityContextHolder.getContext().setAuthentication(new TestingAuthenticationToken("user", "password","ROLE_USER"))
+        when:
+            loadConfig(GlobalMethodSecurityAutowiredConfigAndServicesConfig)
+        then:
+            context.getBean(Service).run()
+    }
+
+    @Configuration
+    @Import([GlobalMethodSecurityAutowiredConfig,ServicesConfig])
+    static class GlobalMethodSecurityAutowiredConfigAndServicesConfig {}
+
+    @Configuration
+    @EnableGlobalMethodSecurity(securedEnabled = true)
+    static class GlobalMethodSecurityAutowiredConfig {
+        @Autowired
+        public void configureGlobal(AuthenticationManagerBuilder auth) {
+            auth.inMemoryAuthentication().withUser("user").password("password").roles("USER")
+        }
+    }
+
+    def "Ordering Autowired on EnableWebSecurity"() {
+        setup:
+            SecurityContextHolder.getContext().setAuthentication(new TestingAuthenticationToken("user", "password","ROLE_USER"))
+        when:
+            loadConfig(GlobalMethodSecurityConfigAndServicesConfig)
+        then:
+            context.getBean(Service).run()
+    }
+
+    @Configuration
+    @Import([GlobalMethodSecurityConfig,WebSecurityConfig,ServicesConfig])
+    static class GlobalMethodSecurityConfigAndServicesConfig {}
+
+    @Configuration
+    @EnableGlobalMethodSecurity(securedEnabled = true)
+    static class GlobalMethodSecurityConfig {}
+
+    @Configuration
+    @EnableWebSecurity
+    static class WebSecurityConfig extends WebSecurityConfigurerAdapter {
+        @Autowired
+        public void configureGlobal(AuthenticationManagerBuilder auth) {
+            auth.inMemoryAuthentication().withUser("user").password("password").roles("USER")
+        }
+    }
+
+    //
+
+    def "Ordering Autowired on EnableWebMvcSecurity"() {
+        setup:
+            SecurityContextHolder.getContext().setAuthentication(new TestingAuthenticationToken("user", "password","ROLE_USER"))
+        when:
+            loadConfig(GlobalMethodSecurityMvcSecurityAndServicesConfig)
+        then:
+            context.getBean(Service).run()
+    }
+
+    @Configuration
+    @Import([GlobalMethodSecurityConfig,WebMvcSecurityConfig,ServicesConfig])
+    static class GlobalMethodSecurityMvcSecurityAndServicesConfig {}
+
+    @Configuration
+    @EnableWebMvcSecurity
+    static class WebMvcSecurityConfig extends WebSecurityConfigurerAdapter {
+        @Autowired
+        public void configureGlobal(AuthenticationManagerBuilder auth) {
+            auth.inMemoryAuthentication().withUser("user").password("password").roles("USER")
+        }
+    }
+
+    //
+
+    def "no authentication getAuthenticationManager falls back to null"() {
+        when:
+            loadConfig(AuthenticationConfiguration,ObjectPostProcessorConfiguration)
+        then:
+            context.getBean(AuthenticationConfiguration).authenticationManager == null
+    }
+
+    def "QuiesentGlobalAuthenticationConfiguererAdapter falls back to null"() {
+        when:
+            loadConfig(AuthenticationConfiguration,ObjectPostProcessorConfiguration,QuiesentGlobalAuthenticationConfiguererAdapter)
+        then:
+            context.getBean(AuthenticationConfiguration).authenticationManager == null
+    }
+
+    @Configuration
+    static class QuiesentGlobalAuthenticationConfiguererAdapter extends GlobalAuthenticationConfigurerAdapter {}
+
+    //
+
+    def "GlobalAuthenticationConfiguererAdapterImpl configures authentication successfully"() {
+        setup:
+            def token = new UsernamePasswordAuthenticationToken("user", "password")
+        when:
+            loadConfig(AuthenticationConfiguration,ObjectPostProcessorConfiguration,GlobalAuthenticationConfiguererAdapterImpl)
+        then:
+            context.getBean(AuthenticationConfiguration).authenticationManager.authenticate(token)?.name == "user"
+    }
+
+    @Configuration
+    static class GlobalAuthenticationConfiguererAdapterImpl extends GlobalAuthenticationConfigurerAdapter {
+        public void init(AuthenticationManagerBuilder auth) throws Exception {
+            auth.inMemoryAuthentication().withUser("user").password("password").roles("USER")
+        }
+    }
+
+    //
+
+    def "AuthenticationManagerBean configures authentication successfully"() {
+        setup:
+            def token = new UsernamePasswordAuthenticationToken("user", "password")
+            def auth = new UsernamePasswordAuthenticationToken("user", "password", AuthorityUtils.createAuthorityList("ROLE_USER"))
+            AuthenticationManagerBeanConfig.AM = Mock(AuthenticationManager)
+            1 * AuthenticationManagerBeanConfig.AM.authenticate(token) >> auth
+        when:
+            loadConfig(AuthenticationConfiguration,ObjectPostProcessorConfiguration,AuthenticationManagerBeanConfig)
+        then:
+            context.getBean(AuthenticationConfiguration).authenticationManager.authenticate(token).name == auth.name
+    }
+
+    @Configuration
+    static class AuthenticationManagerBeanConfig {
+        static AuthenticationManager AM
+        @Bean
+        public AuthenticationManager authenticationManager() {
+            AM
+        }
+    }
+
+    //
+
+    @Configuration
+    static class ServicesConfig {
+        @Bean
+        public Service service() {
+            return new ServiceImpl()
+        }
+    }
+
+    static interface Service {
+        public void run();
+    }
+
+    static class ServiceImpl implements Service {
+        @Secured("ROLE_USER")
+        public void run() {}
+    }
+
+    //
+
+    def "GlobalAuthenticationConfigurerAdapter are ordered"() {
+        setup:
+            loadConfig(AuthenticationConfiguration,ObjectPostProcessorConfiguration)
+            AuthenticationConfiguration config = context.getBean(AuthenticationConfiguration)
+            config.setGlobalAuthenticationConfigurers([new LowestOrderGlobalAuthenticationConfigurerAdapter(), new HighestOrderGlobalAuthenticationConfigurerAdapter(), new DefaultOrderGlobalAuthenticationConfigurerAdapter()])
+        when:
+            config.getAuthenticationManager()
+        then:
+            DefaultOrderGlobalAuthenticationConfigurerAdapter.inits == [HighestOrderGlobalAuthenticationConfigurerAdapter,DefaultOrderGlobalAuthenticationConfigurerAdapter,LowestOrderGlobalAuthenticationConfigurerAdapter]
+            DefaultOrderGlobalAuthenticationConfigurerAdapter.configs == [HighestOrderGlobalAuthenticationConfigurerAdapter,DefaultOrderGlobalAuthenticationConfigurerAdapter,LowestOrderGlobalAuthenticationConfigurerAdapter]
+
+    }
+
+    static class DefaultOrderGlobalAuthenticationConfigurerAdapter extends GlobalAuthenticationConfigurerAdapter {
+        static List inits = []
+        static List configs = []
+
+        public void init(AuthenticationManagerBuilder auth) throws Exception {
+            inits.add(getClass())
+        }
+
+        public void configure(AuthenticationManagerBuilder auth) throws Exception {
+            configs.add(getClass())
+        }
+    }
+
+    @Order(Ordered.LOWEST_PRECEDENCE)
+    static class LowestOrderGlobalAuthenticationConfigurerAdapter extends DefaultOrderGlobalAuthenticationConfigurerAdapter {}
+
+    @Order(Ordered.HIGHEST_PRECEDENCE)
+    static class HighestOrderGlobalAuthenticationConfigurerAdapter extends DefaultOrderGlobalAuthenticationConfigurerAdapter {}
+
+    //
+
+    def "Spring Boot not triggered when already configured"() {
+        setup:
+            loadConfig(AuthenticationConfiguration,ObjectPostProcessorConfiguration)
+            AuthenticationConfiguration config = context.getBean(AuthenticationConfiguration)
+            config.setGlobalAuthenticationConfigurers([new ConfiguresInMemoryConfigurerAdapter(), new BootGlobalAuthenticationConfigurerAdapter()])
+            AuthenticationManager authenticationManager = config.authenticationManager
+        when:
+            authenticationManager.authenticate(new UsernamePasswordAuthenticationToken("user","password"))
+        then:
+            noExceptionThrown()
+        when:
+            authenticationManager.authenticate(new UsernamePasswordAuthenticationToken("boot","password"))
+        then:
+            thrown(AuthenticationException)
+    }
+
+
+    def "Spring Boot is triggered when not already configured"() {
+        setup:
+            loadConfig(AuthenticationConfiguration,ObjectPostProcessorConfiguration)
+            AuthenticationConfiguration config = context.getBean(AuthenticationConfiguration)
+            config.setGlobalAuthenticationConfigurers([new BootGlobalAuthenticationConfigurerAdapter()])
+            AuthenticationManager authenticationManager = config.authenticationManager
+        when:
+            authenticationManager.authenticate(new UsernamePasswordAuthenticationToken("boot","password"))
+        then:
+            noExceptionThrown()
+    }
+
+    static class ConfiguresInMemoryConfigurerAdapter extends GlobalAuthenticationConfigurerAdapter {
+
+        public void init(AuthenticationManagerBuilder auth) throws Exception {
+            auth
+                .inMemoryAuthentication()
+                    .withUser("user").password("password").roles("USER")
+        }
+    }
+
+    @Order(Ordered.LOWEST_PRECEDENCE)
+    static class BootGlobalAuthenticationConfigurerAdapter extends DefaultOrderGlobalAuthenticationConfigurerAdapter {
+        public void init(AuthenticationManagerBuilder auth) throws Exception {
+            auth.apply(new DefaultBootGlobalAuthenticationConfigurerAdapter())
+        }
+    }
+
+    static class DefaultBootGlobalAuthenticationConfigurerAdapter extends DefaultOrderGlobalAuthenticationConfigurerAdapter {
+        @Override
+        public void configure(AuthenticationManagerBuilder auth) throws Exception {
+            if(auth.isConfigured()) {
+                return;
+            }
+
+            User user = new User("boot","password", AuthorityUtils.createAuthorityList("ROLE_USER"))
+
+            List<User> users = Arrays.asList(user);
+            InMemoryUserDetailsManager inMemory = new InMemoryUserDetailsManager(users);
+
+            DaoAuthenticationProvider provider = new DaoAuthenticationProvider()
+            provider.userDetailsService = inMemory
+
+            auth.authenticationProvider(provider)
+        }
+    }
+}

+ 68 - 0
config/src/test/groovy/org/springframework/security/config/annotation/issue50/ApplicationConfig.java

@@ -0,0 +1,68 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.security.config.annotation.issue50;
+
+import javax.sql.DataSource;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
+import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
+import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
+import org.springframework.orm.jpa.JpaTransactionManager;
+import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
+import org.springframework.orm.jpa.vendor.Database;
+import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
+import org.springframework.security.config.annotation.issue50.domain.User;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+
+/**
+ * @author Rob Winch
+ *
+ */
+@Configuration
+@EnableJpaRepositories("org.springframework.security.config.annotation.issue50.repo")
+@EnableTransactionManagement
+public class ApplicationConfig {
+    @Bean
+    public DataSource dataSource() {
+        EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
+        return builder.setType(EmbeddedDatabaseType.HSQL).build();
+    }
+
+    @Bean
+    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
+        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
+        vendorAdapter.setDatabase(Database.HSQL);
+        vendorAdapter.setGenerateDdl(true);
+        vendorAdapter.setShowSql(true);
+
+        LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
+        factory.setJpaVendorAdapter(vendorAdapter);
+        factory.setPackagesToScan(User.class.getPackage().getName());
+        factory.setDataSource(dataSource());
+
+        return factory;
+    }
+
+    @Bean
+    public PlatformTransactionManager transactionManager() {
+        JpaTransactionManager txManager = new JpaTransactionManager();
+        txManager.setEntityManagerFactory(entityManagerFactory().getObject());
+        return txManager;
+    }
+}

+ 100 - 0
config/src/test/groovy/org/springframework/security/config/annotation/issue50/Issue50Tests.groovy

@@ -0,0 +1,100 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.security.config.annotation.issue50;
+
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.security.access.AccessDeniedException
+import org.springframework.security.authentication.AuthenticationManager
+import org.springframework.security.authentication.BadCredentialsException
+import org.springframework.security.authentication.TestingAuthenticationToken
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
+import org.springframework.security.config.annotation.issue50.domain.User
+import org.springframework.security.config.annotation.issue50.repo.UserRepository
+import org.springframework.security.core.Authentication
+import org.springframework.security.core.context.SecurityContextHolder
+import org.springframework.security.core.userdetails.UsernameNotFoundException
+import org.springframework.security.web.FilterChainProxy
+import org.springframework.test.context.ContextConfiguration
+import org.springframework.transaction.annotation.Transactional
+
+import spock.lang.Specification
+
+/**
+ * @author Rob Winch
+ *
+ */
+@ContextConfiguration(classes=[ApplicationConfig,SecurityConfig])
+@Transactional
+class Issue50Tests extends Specification {
+    @Autowired
+    private FilterChainProxy springSecurityFilterChain
+    @Autowired
+    private AuthenticationManager authenticationManager
+    @Autowired
+    private UserRepository userRepo
+
+    def setup() {
+        SecurityContextHolder.context.authentication = new TestingAuthenticationToken("test",null,"ROLE_ADMIN")
+    }
+
+    def cleanup() {
+        SecurityContextHolder.clearContext()
+    }
+
+    // https://github.com/SpringSource/spring-security-javaconfig/issues/50
+    def "#50 - GlobalMethodSecurityConfiguration should load AuthenticationManager lazily"() {
+        when:
+        "Configuration Loads"
+        then: "GlobalMethodSecurityConfiguration loads AuthenticationManager lazily"
+        noExceptionThrown()
+    }
+
+    def "AuthenticationManager will not authenticate missing user"() {
+        when:
+        authenticationManager.authenticate(new UsernamePasswordAuthenticationToken("test", "password"))
+        then:
+        thrown(UsernameNotFoundException)
+    }
+
+    def "AuthenticationManager will not authenticate with invalid password"() {
+        when:
+        User user = new User(username:"test",password:"password")
+        userRepo.save(user)
+        authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.username , "invalid"))
+        then:
+        thrown(BadCredentialsException)
+    }
+
+    def "AuthenticationManager can be used to authenticate a user"() {
+        when:
+        User user = new User(username:"test",password:"password")
+        userRepo.save(user)
+        Authentication result = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.username , user.password))
+        then:
+        result.principal == user.username
+    }
+
+    def "Global Method Security is enabled and works"() {
+        setup:
+        SecurityContextHolder.context.authentication = new TestingAuthenticationToken("test",null,"ROLE_USER")
+        when:
+        User user = new User(username:"denied",password:"password")
+        userRepo.save(user)
+        Authentication result = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.username , user.password))
+        then:
+        thrown(AccessDeniedException)
+    }
+}

+ 90 - 0
config/src/test/groovy/org/springframework/security/config/annotation/issue50/SecurityConfig.java

@@ -0,0 +1,90 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.security.config.annotation.issue50;
+
+import org.spockframework.util.Assert;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.authentication.TestingAuthenticationToken;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.issue50.domain.User;
+import org.springframework.security.config.annotation.issue50.repo.UserRepository;
+import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+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.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+
+/**
+ * @author Rob Winch
+ *
+ */
+@EnableWebSecurity
+@EnableGlobalMethodSecurity(prePostEnabled = true)
+@Configuration
+public class SecurityConfig extends WebSecurityConfigurerAdapter {
+    @Autowired
+    private UserRepository myUserRepository;
+
+    @Override
+    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
+        auth
+            .authenticationProvider(authenticationProvider());
+    }
+
+    @Override
+    protected void configure(HttpSecurity http) throws Exception {
+        http
+            .authorizeRequests()
+                .antMatchers("/*").permitAll();
+    }
+
+    @Bean
+    @Override
+    public AuthenticationManager authenticationManagerBean()
+            throws Exception {
+        return super.authenticationManagerBean();
+    }
+
+    @Bean
+    public AuthenticationProvider authenticationProvider() {
+        Assert.notNull(myUserRepository);
+        return new AuthenticationProvider() {
+            public boolean supports(Class<?> authentication) {
+                return true;
+            }
+            public Authentication authenticate(Authentication authentication)
+                    throws AuthenticationException {
+                Object principal = authentication.getPrincipal();
+                String username = String.valueOf(principal);
+                User user = myUserRepository.findByUsername(username);
+                if(user == null) {
+                    throw new UsernameNotFoundException("No user for principal "+principal);
+                }
+                if(!authentication.getCredentials().equals(user.getPassword())) {
+                    throw new BadCredentialsException("Invalid password");
+                }
+                return new TestingAuthenticationToken(principal, null, "ROLE_USER");
+            }
+        };
+    }
+}

+ 62 - 0
config/src/test/groovy/org/springframework/security/config/annotation/issue50/domain/User.java

@@ -0,0 +1,62 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.security.config.annotation.issue50.domain;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+
+/**
+ * @author Rob Winch
+ *
+ */
+@Entity
+public class User {
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.AUTO)
+    private Long id;
+
+    private String username;
+
+    private String password;
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public String getUsername() {
+        return username;
+    }
+
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+}

+ 30 - 0
config/src/test/groovy/org/springframework/security/config/annotation/issue50/repo/UserRepository.java

@@ -0,0 +1,30 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.security.config.annotation.issue50.repo;
+
+import org.springframework.data.repository.CrudRepository;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.security.config.annotation.issue50.domain.User;
+
+/**
+ * @author Rob Winch
+ *
+ */
+public interface UserRepository extends CrudRepository<User, String> {
+
+    @PreAuthorize("hasRole('ROLE_ADMIN')")
+    User findByUsername(String username);
+}

+ 14 - 8
config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/Issue55Tests.groovy

@@ -19,6 +19,7 @@ import org.springframework.context.annotation.Bean
 import org.springframework.context.annotation.Configuration
 import org.springframework.core.annotation.Order;
 import org.springframework.security.authentication.AuthenticationManager
+import org.springframework.security.authentication.TestingAuthenticationToken;
 import org.springframework.security.config.annotation.BaseSpringSpec
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@@ -36,11 +37,13 @@ import org.springframework.stereotype.Component
 class Issue55Tests extends BaseSpringSpec {
 
     def "WebSecurityConfigurerAdapter defaults to @Autowired"() {
+        setup:
+            TestingAuthenticationToken token = new TestingAuthenticationToken("test", "this")
         when:
-        loadConfig(WebSecurityConfigurerAdapterDefaultsAuthManagerConfig)
+            loadConfig(WebSecurityConfigurerAdapterDefaultsAuthManagerConfig)
         then:
-        context.getBean(FilterChainProxy)
-        findFilter(FilterSecurityInterceptor).authenticationManager.parent.class == CustomAuthenticationManager
+            context.getBean(FilterChainProxy)
+            findFilter(FilterSecurityInterceptor).authenticationManager.authenticate(token) == CustomAuthenticationManager.RESULT
      }
 
     @Configuration
@@ -66,12 +69,14 @@ class Issue55Tests extends BaseSpringSpec {
     }
 
     def "multi http WebSecurityConfigurerAdapter defaults to @Autowired"() {
+        setup:
+            TestingAuthenticationToken token = new TestingAuthenticationToken("test", "this")
         when:
-        loadConfig(MultiWebSecurityConfigurerAdapterDefaultsAuthManagerConfig)
+            loadConfig(MultiWebSecurityConfigurerAdapterDefaultsAuthManagerConfig)
         then:
-        context.getBean(FilterChainProxy)
-        findFilter(FilterSecurityInterceptor).authenticationManager.parent.class == CustomAuthenticationManager
-        findFilter(FilterSecurityInterceptor,1).authenticationManager.parent.class == CustomAuthenticationManager
+            context.getBean(FilterChainProxy)
+            findFilter(FilterSecurityInterceptor).authenticationManager.authenticate(token) == CustomAuthenticationManager.RESULT
+            findFilter(FilterSecurityInterceptor,1).authenticationManager.authenticate(token) == CustomAuthenticationManager.RESULT
      }
 
     @Configuration
@@ -107,8 +112,9 @@ class Issue55Tests extends BaseSpringSpec {
     }
 
     static class CustomAuthenticationManager implements AuthenticationManager {
+        static Authentication RESULT = new TestingAuthenticationToken("test", "this","ROLE_USER")
         public Authentication authenticate(Authentication authentication) throws AuthenticationException {
-            return null;
+            return RESULT;
         }
     }
 }

+ 2 - 0
docs/guides/src/asciidoc/_hello-includes/secure-the-application.asc

@@ -68,6 +68,8 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
 }
 ----
 
+NOTE: The name of the configureGlobal method is not important. However, it is important to only configure AuthenticationManagerBuilder in a class annotated with either `@EnableWebSecurity`, `@EnableWebMvcSecurity`, `@EnableGlobalMethodSecurity`, or `@EnableGlobalAuthentication`. Doing otherwise has unpredictable results.
+
 [[servlet-api-integration]]
 The <<security-config-java,SecurityConfig>> will:
 

+ 2 - 0
docs/manual/src/asciidoc/index.adoc

@@ -431,6 +431,8 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
 }
 ----
 
+NOTE: The name of the configureGlobal method is not important. However, it is important to only configure AuthenticationManagerBuilder in a class annotated with either `@EnableWebSecurity`, `@EnableWebMvcSecurity`, `@EnableGlobalMethodSecurity`, or `@EnableGlobalAuthentication`. Doing otherwise has unpredictable results.
+
 There really isn't much to this configuration, but it does a lot. You can find a summary of the features below:
 
 * Require authentication to every URL in your application