Make a lazy AuthenticationManager if we think it's already configured

Instead of just blindly creating the default authentication manager, after
thic change we count the beans of type GlobalAuthenticationManagerConfigurer
and assume that if we detect more than we expect (one from Boot and one from
Spring Security) then the user is telling us they want to configure the
AuthenticationManager themselves.

Fixes gh-1801
This commit is contained in:
Dave Syer 2014-11-01 17:41:52 +00:00
parent 0f17142366
commit b20d02a31d
2 changed files with 139 additions and 15 deletions

View File

@ -32,7 +32,6 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Primary;
import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationEventPublisher; import org.springframework.security.authentication.AuthenticationEventPublisher;
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManager;
@ -42,6 +41,8 @@ import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.SecurityConfigurer; import org.springframework.security.config.annotation.SecurityConfigurer;
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.security.config.annotation.authentication.configurers.GlobalAuthenticationConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
/** /**
@ -57,8 +58,8 @@ import org.springframework.stereotype.Component;
*/ */
@Configuration @Configuration
@ConditionalOnBean(ObjectPostProcessor.class) @ConditionalOnBean(ObjectPostProcessor.class)
@ConditionalOnMissingBean(AuthenticationManager.class) @ConditionalOnMissingBean({ AuthenticationManager.class })
@Order(Ordered.LOWEST_PRECEDENCE - 3) @Order(0)
public class AuthenticationManagerConfiguration extends public class AuthenticationManagerConfiguration extends
GlobalAuthenticationConfigurerAdapter { GlobalAuthenticationConfigurerAdapter {
@ -84,18 +85,27 @@ public class AuthenticationManagerConfiguration extends
@Bean @Bean
@Primary @Primary
public AuthenticationManager authenticationManager(AuthenticationManagerBuilder auth) public AuthenticationManager authenticationManager(AuthenticationManagerBuilder auth,
throws Exception { ApplicationContext context) throws Exception {
if (isAuthenticationManagerAlreadyConfigured(context)) {
return new LazyAuthenticationManager(auth);
}
/* /*
* This AuthenticationManagerBuilder is for the global AuthenticationManager * This AuthenticationManagerBuilder is for the global AuthenticationManager
*/ */
BootDefaultingAuthenticationConfigurerAdapter configurer = new BootDefaultingAuthenticationConfigurerAdapter(); BootDefaultingAuthenticationConfigurerAdapter configurer = new BootDefaultingAuthenticationConfigurerAdapter();
configurer.init(auth);
configurer.configure(auth); configurer.configure(auth);
AuthenticationManager manager = configurer.getAuthenticationManagerBuilder() AuthenticationManager manager = configurer.getAuthenticationManagerBuilder()
.getOrBuild(); .getOrBuild();
configurer.configureParent(auth); configurer.configureParent(auth);
return manager; return manager;
}
private boolean isAuthenticationManagerAlreadyConfigured(ApplicationContext context) {
return context.getBeanNamesForType(GlobalAuthenticationConfigurerAdapter.class).length > 2;
} }
@Component @Component
@ -142,8 +152,7 @@ public class AuthenticationManagerConfiguration extends
* methods are invoked before configure, which cannot be guaranteed at this point.</li> * methods are invoked before configure, which cannot be guaranteed at this point.</li>
* </ul> * </ul>
*/ */
private class BootDefaultingAuthenticationConfigurerAdapter extends private class BootDefaultingAuthenticationConfigurerAdapter {
GlobalAuthenticationConfigurerAdapter {
private AuthenticationManagerBuilder defaultAuth; private AuthenticationManagerBuilder defaultAuth;
@ -159,7 +168,6 @@ public class AuthenticationManagerConfiguration extends
return this.defaultAuth; return this.defaultAuth;
} }
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception { public void configure(AuthenticationManagerBuilder auth) throws Exception {
if (auth.isConfigured()) { if (auth.isConfigured()) {
this.defaultAuth = auth; this.defaultAuth = auth;
@ -188,4 +196,20 @@ public class AuthenticationManagerConfiguration extends
} }
} }
private static class LazyAuthenticationManager implements AuthenticationManager {
private AuthenticationManagerBuilder builder;
public LazyAuthenticationManager(AuthenticationManagerBuilder builder) {
this.builder = builder;
}
@Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
return builder.getOrBuild().authenticate(authentication);
}
}
} }

View File

@ -16,11 +16,17 @@
package org.springframework.boot.autoconfigure.security; package org.springframework.boot.autoconfigure.security;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import org.junit.After; import org.junit.After;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.TestAutoConfigurationPackage; import org.springframework.boot.autoconfigure.TestAutoConfigurationPackage;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
@ -32,6 +38,7 @@ import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener; import org.springframework.context.ApplicationListener;
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.Order;
import org.springframework.mock.web.MockServletContext; import org.springframework.mock.web.MockServletContext;
import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManager;
@ -40,17 +47,16 @@ import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.event.AuthenticationFailureBadCredentialsEvent; import org.springframework.security.authentication.event.AuthenticationFailureBadCredentialsEvent;
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.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.web.FilterChainProxy; import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/** /**
* Tests for {@link SecurityAutoConfiguration}. * Tests for {@link SecurityAutoConfiguration}.
* *
@ -138,7 +144,8 @@ public class SecurityAutoConfigurationTests {
catch (BadCredentialsException e) { catch (BadCredentialsException e) {
// expected // expected
} }
assertTrue(wrapper.get() instanceof AuthenticationFailureBadCredentialsEvent); assertTrue("Wrong event type: " + wrapper.get(),
wrapper.get() instanceof AuthenticationFailureBadCredentialsEvent);
} }
@Test @Test
@ -154,6 +161,55 @@ public class SecurityAutoConfigurationTests {
this.context.getBean(AuthenticationManager.class)); this.context.getBean(AuthenticationManager.class));
} }
@Test
public void testOverrideAuthenticationManagerAndInjectIntoSecurityFilter()
throws Exception {
this.context = new AnnotationConfigWebApplicationContext();
this.context.setServletContext(new MockServletContext());
this.context.register(TestAuthenticationConfiguration.class,
SecurityCustomizer.class, SecurityAutoConfiguration.class,
ServerPropertiesAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class);
this.context.refresh();
assertEquals(
this.context.getBean(TestAuthenticationConfiguration.class).authenticationManager,
this.context.getBean(AuthenticationManager.class));
}
@Test
public void testOverrideAuthenticationManagerWithBuilderAndInjectIntoSecurityFilter()
throws Exception {
this.context = new AnnotationConfigWebApplicationContext();
this.context.setServletContext(new MockServletContext());
this.context.register(AuthenticationManagerCustomizer.class,
SecurityCustomizer.class, SecurityAutoConfiguration.class,
ServerPropertiesAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class);
this.context.refresh();
UsernamePasswordAuthenticationToken user = new UsernamePasswordAuthenticationToken(
"foo", "bar",
AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER"));
assertNotNull(this.context.getBean(AuthenticationManager.class)
.authenticate(user));
}
@Test
public void testOverrideAuthenticationManagerWithBuilderAndInjectBuilderIntoSecurityFilter()
throws Exception {
this.context = new AnnotationConfigWebApplicationContext();
this.context.setServletContext(new MockServletContext());
this.context.register(AuthenticationManagerCustomizer.class,
WorkaroundSecurityCustomizer.class, SecurityAutoConfiguration.class,
ServerPropertiesAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class);
this.context.refresh();
UsernamePasswordAuthenticationToken user = new UsernamePasswordAuthenticationToken(
"foo", "bar",
AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER"));
assertNotNull(this.context.getBean(AuthenticationManager.class)
.authenticate(user));
}
@Test @Test
public void testJpaCoexistsHappily() throws Exception { public void testJpaCoexistsHappily() throws Exception {
this.context = new AnnotationConfigWebApplicationContext(); this.context = new AnnotationConfigWebApplicationContext();
@ -196,4 +252,48 @@ public class SecurityAutoConfigurationTests {
} }
@Configuration
protected static class SecurityCustomizer extends WebSecurityConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
}
@Configuration
protected static class WorkaroundSecurityCustomizer extends
WebSecurityConfigurerAdapter {
@Autowired
private AuthenticationManagerBuilder builder;
@SuppressWarnings("unused")
private AuthenticationManager authenticationManager;
@Override
protected void configure(HttpSecurity http) throws Exception {
this.authenticationManager = new AuthenticationManager() {
@Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
return WorkaroundSecurityCustomizer.this.builder.getOrBuild()
.authenticate(authentication);
}
};
}
}
@Configuration
@Order(-1)
protected static class AuthenticationManagerCustomizer extends
GlobalAuthenticationConfigurerAdapter {
@Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("foo").password("bar").roles("USER");
}
}
} }