diff --git a/config/config.gradle b/config/config.gradle index 986aa6e403..579972d887 100644 --- a/config/config.gradle +++ b/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" } diff --git a/config/src/main/java/org/springframework/security/config/annotation/authentication/builders/AuthenticationManagerBuilder.java b/config/src/main/java/org/springframework/security/config/annotation/authentication/builders/AuthenticationManagerBuilder.java index 5ac8af4e86..68dbef25f6 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/authentication/builders/AuthenticationManagerBuilder.java +++ b/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. + * + *

+ * 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 diff --git a/config/src/main/java/org/springframework/security/config/annotation/authentication/configuration/AuthenticationConfiguration.java b/config/src/main/java/org/springframework/security/config/annotation/authentication/configuration/AuthenticationConfiguration.java index 75cc62c27f..3cb1d22365 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/authentication/configuration/AuthenticationConfiguration.java +++ b/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 globalAuthConfigures = Collections.emptyList(); + + private ObjectPostProcessor objectPostProcessor; @Bean public AuthenticationManagerBuilder authenticationManagerBuilder(ObjectPostProcessor 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 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 objectPostProcessor) { + this.objectPostProcessor = objectPostProcessor; + } + + + @SuppressWarnings("unchecked") + private T lazyBean(Class 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 beansWithAnnotation = context.getBeansWithAnnotation(EnableGlobalAuthentication.class); + if(logger.isDebugEnabled()) { + logger.debug("Eagerly initializing " + beansWithAnnotation); + } + } + } +} \ No newline at end of file diff --git a/config/src/main/java/org/springframework/security/config/annotation/authentication/configuration/EnableGlobalAuthentication.java b/config/src/main/java/org/springframework/security/config/annotation/authentication/configuration/EnableGlobalAuthentication.java new file mode 100644 index 0000000000..bca6076a36 --- /dev/null +++ b/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: + * + *
+ * @Configuration
+ * @EnableGlobalAuthentication
+ * public class MyGlobalAuthenticationConfiguration {
+ *
+ *     @Autowired
+ *     public void configureGlobal(AuthenticationManagerBuilder auth) {
+ *        auth
+ *            .inMemoryAuthentication()
+ *                .withUser("user").password("password").roles("USER").and()
+ *                .withUser("admin").password("password").roles("USER", "ADMIN");
+ *     }
+ * }
+ * 
+ * + * 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: + * + *
+ * @Configuration
+ * @EnableWebSecurity
+ * public class MyWebSecurityConfiguration extends WebSecurityConfigurerAdapter {
+ *
+ *    @Autowired
+ *    public void configureGlobal(AuthenticationManagerBuilder auth) {
+ *        auth
+ *            .inMemoryAuthentication()
+ *                .withUser("user").password("password").roles("USER").and()
+ *                .withUser("admin").password("password").roles("USER", "ADMIN");
+ *    }
+ *
+ *    // Possibly overridden methods ...
+ * }
+ * 
+ * + * The following annotations are annotated with {@link EnableGlobalAuthentication} + * + *
    + *
  • {@link EnableWebSecurity}
  • + *
  • {@link EnableWebMvcSecurity}
  • + *
  • {@link EnableGlobalMethodSecurity}
  • + *
+ * + * 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 {} diff --git a/config/src/main/java/org/springframework/security/config/annotation/authentication/configurers/GlobalAuthenticationConfigurerAdapter.java b/config/src/main/java/org/springframework/security/config/annotation/authentication/configurers/GlobalAuthenticationConfigurerAdapter.java new file mode 100644 index 0000000000..2bc1e13cd2 --- /dev/null +++ b/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 { + + public void init(AuthenticationManagerBuilder auth) throws Exception {} + + public void configure(AuthenticationManagerBuilder auth) throws Exception {} +} \ No newline at end of file diff --git a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/EnableGlobalMethodSecurity.java b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/EnableGlobalMethodSecurity.java index f8ebc50991..8623e3125e 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/EnableGlobalMethodSecurity.java +++ b/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 { /** diff --git a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/GlobalMethodSecurityConfiguration.java b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/GlobalMethodSecurityConfiguration.java index 0eccb8d978..31a2faee3e 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/GlobalMethodSecurityConfiguration.java +++ b/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 objectPostProcessor = new ObjectPostProcessor() { public 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 objectPostProcessor) { this.objectPostProcessor = objectPostProcessor; @@ -370,17 +353,14 @@ public class GlobalMethodSecurityConfiguration implements ImportAware { this.defaultMethodExpressionHandler.setPermissionEvaluator(permissionEvaluators.get(0)); } - @SuppressWarnings("unchecked") - private T lazyBean(Class 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; } - -} +} \ No newline at end of file diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configuration/EnableWebSecurity.java b/config/src/main/java/org/springframework/security/config/annotation/web/configuration/EnableWebSecurity.java index 6584668613..a22eecef5e 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configuration/EnableWebSecurity.java +++ b/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 { /** diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurerAdapter.java b/config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurerAdapter.java index 330f235979..865d8e63b8 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurerAdapter.java +++ b/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 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 { diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/servlet/configuration/EnableWebMvcSecurity.java b/config/src/main/java/org/springframework/security/config/annotation/web/servlet/configuration/EnableWebMvcSecurity.java index 29cd283024..c836fd098e 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/servlet/configuration/EnableWebMvcSecurity.java +++ b/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 { } diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/servlet/configuration/WebMvcSecurityConfiguration.java b/config/src/main/java/org/springframework/security/config/annotation/web/servlet/configuration/WebMvcSecurityConfiguration.java index 01c3d54665..984d6611bb 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/servlet/configuration/WebMvcSecurityConfiguration.java +++ b/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(); } -} +} \ No newline at end of file diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/authentication/AuthenticationManagerBuilderTests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/authentication/AuthenticationManagerBuilderTests.groovy index d61f1e044f..9f23e07311 100644 --- a/config/src/test/groovy/org/springframework/security/config/annotation/authentication/AuthenticationManagerBuilderTests.groovy +++ b/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 + } } diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/authentication/configuration/AuthenticationConfigurationTests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/authentication/configuration/AuthenticationConfigurationTests.groovy new file mode 100644 index 0000000000..c1dc0d9ef6 --- /dev/null +++ b/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 users = Arrays.asList(user); + InMemoryUserDetailsManager inMemory = new InMemoryUserDetailsManager(users); + + DaoAuthenticationProvider provider = new DaoAuthenticationProvider() + provider.userDetailsService = inMemory + + auth.authenticationProvider(provider) + } + } +} \ No newline at end of file diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/issue50/ApplicationConfig.java b/config/src/test/groovy/org/springframework/security/config/annotation/issue50/ApplicationConfig.java new file mode 100644 index 0000000000..4f0752951c --- /dev/null +++ b/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; + } +} diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/issue50/Issue50Tests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/issue50/Issue50Tests.groovy new file mode 100644 index 0000000000..dc17ca99e3 --- /dev/null +++ b/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) + } +} diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/issue50/SecurityConfig.java b/config/src/test/groovy/org/springframework/security/config/annotation/issue50/SecurityConfig.java new file mode 100644 index 0000000000..9ba5b09ed9 --- /dev/null +++ b/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"); + } + }; + } +} diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/issue50/domain/User.java b/config/src/test/groovy/org/springframework/security/config/annotation/issue50/domain/User.java new file mode 100644 index 0000000000..1ba7afcf8e --- /dev/null +++ b/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; + } + +} \ No newline at end of file diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/issue50/repo/UserRepository.java b/config/src/test/groovy/org/springframework/security/config/annotation/issue50/repo/UserRepository.java new file mode 100644 index 0000000000..915182caf3 --- /dev/null +++ b/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 { + + @PreAuthorize("hasRole('ROLE_ADMIN')") + User findByUsername(String username); +} \ No newline at end of file diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/Issue55Tests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/Issue55Tests.groovy index af84d074b6..05270b110e 100644 --- a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/Issue55Tests.groovy +++ b/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; } } } diff --git a/docs/guides/src/asciidoc/_hello-includes/secure-the-application.asc b/docs/guides/src/asciidoc/_hello-includes/secure-the-application.asc index ff0fcae8bc..dd94eba40b 100644 --- a/docs/guides/src/asciidoc/_hello-includes/secure-the-application.asc +++ b/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 <> will: diff --git a/docs/manual/src/asciidoc/index.adoc b/docs/manual/src/asciidoc/index.adoc index beb8b1725c..6df0da8129 100644 --- a/docs/manual/src/asciidoc/index.adoc +++ b/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