SEC-2447: Fix AuthenticationManagerBuilder ordering issues

This commit is contained in:
Rob Winch 2014-02-06 17:01:53 -06:00
parent c42e13c966
commit 6c35c33abe
21 changed files with 994 additions and 68 deletions

View File

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

View File

@ -27,6 +27,7 @@ import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder; import org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder;
import org.springframework.security.config.annotation.ObjectPostProcessor; import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.SecurityBuilder; 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.ProviderManagerBuilder;
import org.springframework.security.config.annotation.authentication.configurers.ldap.LdapAuthenticationProviderConfigurer; import org.springframework.security.config.annotation.authentication.configurers.ldap.LdapAuthenticationProviderConfigurer;
import org.springframework.security.config.annotation.authentication.configurers.provisioning.InMemoryUserDetailsManagerConfigurer; import org.springframework.security.config.annotation.authentication.configurers.provisioning.InMemoryUserDetailsManagerConfigurer;
@ -221,7 +222,7 @@ public class AuthenticationManagerBuilder extends AbstractConfiguredSecurityBuil
@Override @Override
protected ProviderManager performBuild() throws Exception { protected ProviderManager performBuild() throws Exception {
if(authenticationProviders.isEmpty() && parentAuthenticationManager == null) { if(!isConfigured()) {
logger.debug("No authenticationProviders and no parentAuthenticationManager defined. Returning null."); logger.debug("No authenticationProviders and no parentAuthenticationManager defined. Returning null.");
return null; return null;
} }
@ -236,6 +237,26 @@ public class AuthenticationManagerBuilder extends AbstractConfiguredSecurityBuil
return providerManager; 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 * Gets the default {@link UserDetailsService} for the
* {@link AuthenticationManagerBuilder}. The result may be null in some * {@link AuthenticationManagerBuilder}. The result may be null in some

View File

@ -15,10 +15,25 @@
*/ */
package org.springframework.security.config.annotation.authentication.configuration; 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.Bean;
import org.springframework.context.annotation.Configuration; 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.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 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} * Exports the authentication {@link Configuration}
@ -29,9 +44,98 @@ import org.springframework.security.config.annotation.authentication.builders.Au
*/ */
@Configuration @Configuration
public class AuthenticationConfiguration { public class AuthenticationConfiguration {
private ApplicationContext applicationContext;
private AuthenticationManager authenticationManager;
private boolean authenticationManagerInitialized;
private List<GlobalAuthenticationConfigurerAdapter> globalAuthConfigures = Collections.emptyList();
private ObjectPostProcessor<Object> objectPostProcessor;
@Bean @Bean
public AuthenticationManagerBuilder authenticationManagerBuilder(ObjectPostProcessor<Object> objectPostProcessor) { public AuthenticationManagerBuilder authenticationManagerBuilder(ObjectPostProcessor<Object> objectPostProcessor) {
return new AuthenticationManagerBuilder(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);
}
}
}
} }

View File

@ -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 {}

View File

@ -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 {}
}

View File

@ -23,7 +23,7 @@ import org.springframework.context.annotation.AdviceMode;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.security.access.annotation.Secured; 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; 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) @Retention(value=java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value={java.lang.annotation.ElementType.TYPE}) @Target(value={java.lang.annotation.ElementType.TYPE})
@Documented @Documented
@Import({GlobalMethodSecuritySelector.class,ObjectPostProcessorConfiguration.class,AuthenticationConfiguration.class}) @Import({GlobalMethodSecuritySelector.class,ObjectPostProcessorConfiguration.class})
@EnableGlobalAuthentication
public @interface EnableGlobalMethodSecurity { public @interface EnableGlobalMethodSecurity {
/** /**

View File

@ -16,19 +16,14 @@
package org.springframework.security.config.annotation.method.configuration; package org.springframework.security.config.annotation.method.configuration;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInterceptor;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; 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.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportAware; 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.authentication.DefaultAuthenticationEventPublisher;
import org.springframework.security.config.annotation.ObjectPostProcessor; import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.util.Assert; import org.springframework.util.Assert;
/** /**
@ -81,7 +77,6 @@ import org.springframework.util.Assert;
@Configuration @Configuration
public class GlobalMethodSecurityConfiguration implements ImportAware { public class GlobalMethodSecurityConfiguration implements ImportAware {
private static final Log logger = LogFactory.getLog(GlobalMethodSecurityConfiguration.class); private static final Log logger = LogFactory.getLog(GlobalMethodSecurityConfiguration.class);
private ApplicationContext context;
private ObjectPostProcessor<Object> objectPostProcessor = new ObjectPostProcessor<Object>() { private ObjectPostProcessor<Object> objectPostProcessor = new ObjectPostProcessor<Object>() {
public <T> T postProcess(T object) { public <T> T postProcess(T object) {
throw new IllegalStateException(ObjectPostProcessor.class.getName()+ " is a required bean. Ensure you have used @"+EnableGlobalMethodSecurity.class.getName()); 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 boolean disableAuthenticationRegistry;
private AnnotationAttributes enableMethodSecurity; private AnnotationAttributes enableMethodSecurity;
private MethodSecurityExpressionHandler expressionHandler; private MethodSecurityExpressionHandler expressionHandler;
private AuthenticationConfiguration authenticationConfiguration;
/** /**
* Creates the default MethodInterceptor which is a MethodSecurityInterceptor using the following methods to * 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 = new AuthenticationManagerBuilder(objectPostProcessor);
auth.authenticationEventPublisher(eventPublisher); auth.authenticationEventPublisher(eventPublisher);
configure(auth); configure(auth);
if(!disableAuthenticationRegistry) { if(disableAuthenticationRegistry) {
authenticationManager = getAuthenticationConfiguration().getAuthenticationManager();
} else {
authenticationManager = auth.build(); 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; return authenticationManager;
} }
@ -351,11 +339,6 @@ public class GlobalMethodSecurityConfiguration implements ImportAware {
this.defaultMethodExpressionHandler.setTrustResolver(trustResolver); this.defaultMethodExpressionHandler.setTrustResolver(trustResolver);
} }
@Autowired
public void setApplicationContext(ApplicationContext context) {
this.context = context;
}
@Autowired(required=false) @Autowired(required=false)
public void setObjectPostProcessor(ObjectPostProcessor<Object> objectPostProcessor) { public void setObjectPostProcessor(ObjectPostProcessor<Object> objectPostProcessor) {
this.objectPostProcessor = objectPostProcessor; this.objectPostProcessor = objectPostProcessor;
@ -370,17 +353,14 @@ public class GlobalMethodSecurityConfiguration implements ImportAware {
this.defaultMethodExpressionHandler.setPermissionEvaluator(permissionEvaluators.get(0)); this.defaultMethodExpressionHandler.setPermissionEvaluator(permissionEvaluators.get(0));
} }
@SuppressWarnings("unchecked") @Autowired(required = false)
private <T> T lazyBean(Class<T> interfaceName) { public void setAuthenticationConfiguration(AuthenticationConfiguration authenticationConfiguration) {
LazyInitTargetSource lazyTargetSource = new LazyInitTargetSource(); this.authenticationConfiguration = authenticationConfiguration;
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]); private AuthenticationConfiguration getAuthenticationConfiguration() {
lazyTargetSource.setBeanFactory(context); Assert.notNull(authenticationConfiguration, "authenticationConfiguration cannot be null");
ProxyFactoryBean proxyFactory = new ProxyFactoryBean(); return authenticationConfiguration;
proxyFactory.setTargetSource(lazyTargetSource);
proxyFactory.setInterfaces(new Class[] { interfaceName });
return (T) proxyFactory.getObject();
} }
private boolean prePostEnabled() { private boolean prePostEnabled() {
@ -414,5 +394,4 @@ public class GlobalMethodSecurityConfiguration implements ImportAware {
} }
return this.enableMethodSecurity; return this.enableMethodSecurity;
} }
} }

View File

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

View File

@ -21,7 +21,6 @@ import java.util.List;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.Order; 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.authentication.DefaultAuthenticationEventPublisher;
import org.springframework.security.config.annotation.ObjectPostProcessor; import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 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.WebSecurityConfigurer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity; 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.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor; import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter; 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.ContentNegotiationStrategy;
import org.springframework.web.accept.HeaderContentNegotiationStrategy; import org.springframework.web.accept.HeaderContentNegotiationStrategy;
@ -68,6 +69,7 @@ public abstract class WebSecurityConfigurerAdapter implements WebSecurityConfigu
} }
}; };
private AuthenticationConfiguration authenticationConfiguration;
private AuthenticationManagerBuilder authenticationBuilder; private AuthenticationManagerBuilder authenticationBuilder;
private AuthenticationManagerBuilder parentAuthenticationBuilder; private AuthenticationManagerBuilder parentAuthenticationBuilder;
private boolean disableAuthenticationRegistration; private boolean disableAuthenticationRegistration;
@ -221,18 +223,7 @@ public abstract class WebSecurityConfigurerAdapter implements WebSecurityConfigu
if(!authenticationManagerInitialized) { if(!authenticationManagerInitialized) {
configure(parentAuthenticationBuilder); configure(parentAuthenticationBuilder);
if(disableAuthenticationRegistration) { if(disableAuthenticationRegistration) {
try { authenticationManager = authenticationConfiguration.getAuthenticationManager();
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);
}
}
} else { } else {
authenticationManager = parentAuthenticationBuilder.build(); authenticationManager = parentAuthenticationBuilder.build();
} }
@ -341,7 +332,7 @@ public abstract class WebSecurityConfigurerAdapter implements WebSecurityConfigu
this.contentNegotiationStrategy = contentNegotiationStrategy; this.contentNegotiationStrategy = contentNegotiationStrategy;
} }
@Autowired(required=false) @Autowired
public void setObjectPostProcessor(ObjectPostProcessor<Object> objectPostProcessor) { public void setObjectPostProcessor(ObjectPostProcessor<Object> objectPostProcessor) {
this.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 * Delays the use of the {@link UserDetailsService} from the
@ -416,8 +411,9 @@ public abstract class WebSecurityConfigurerAdapter implements WebSecurityConfigu
private AuthenticationManager delegate; private AuthenticationManager delegate;
private final Object delegateMonitor = new Object(); private final Object delegateMonitor = new Object();
AuthenticationManagerDelegator(AuthenticationManagerBuilder authentication) { AuthenticationManagerDelegator(AuthenticationManagerBuilder delegateBuilder) {
this.delegateBuilder = authentication; Assert.notNull(delegateBuilder,"delegateBuilder cannot be null");
this.delegateBuilder = delegateBuilder;
} }
public Authentication authenticate(Authentication authentication) throws AuthenticationException { public Authentication authenticate(Authentication authentication) throws AuthenticationException {

View File

@ -20,6 +20,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import org.springframework.context.annotation.Import; 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}) @Target(value={java.lang.annotation.ElementType.TYPE})
@Documented @Documented
@Import(WebMvcSecurityConfiguration.class) @Import(WebMvcSecurityConfiguration.class)
@EnableGlobalAuthentication
public @interface EnableWebMvcSecurity { public @interface EnableWebMvcSecurity {
} }

View File

@ -87,4 +87,37 @@ class AuthenticationManagerBuilderTests extends BaseSpringSpec {
.withUser("admin").password("password").roles("USER","ADMIN") .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
}
} }

View File

@ -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)
}
}
}

View File

@ -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;
}
}

View File

@ -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)
}
}

View File

@ -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");
}
};
}
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

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

View File

@ -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]] [[servlet-api-integration]]
The <<security-config-java,SecurityConfig>> will: The <<security-config-java,SecurityConfig>> will:

View File

@ -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: 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 * Require authentication to every URL in your application