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.Primary;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationEventPublisher;
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.authentication.builders.AuthenticationManagerBuilder;
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;
/**
@ -57,8 +58,8 @@ import org.springframework.stereotype.Component;
*/
@Configuration
@ConditionalOnBean(ObjectPostProcessor.class)
@ConditionalOnMissingBean(AuthenticationManager.class)
@Order(Ordered.LOWEST_PRECEDENCE - 3)
@ConditionalOnMissingBean({ AuthenticationManager.class })
@Order(0)
public class AuthenticationManagerConfiguration extends
GlobalAuthenticationConfigurerAdapter {
@ -84,18 +85,27 @@ public class AuthenticationManagerConfiguration extends
@Bean
@Primary
public AuthenticationManager authenticationManager(AuthenticationManagerBuilder auth)
throws Exception {
public AuthenticationManager authenticationManager(AuthenticationManagerBuilder auth,
ApplicationContext context) throws Exception {
if (isAuthenticationManagerAlreadyConfigured(context)) {
return new LazyAuthenticationManager(auth);
}
/*
* This AuthenticationManagerBuilder is for the global AuthenticationManager
*/
BootDefaultingAuthenticationConfigurerAdapter configurer = new BootDefaultingAuthenticationConfigurerAdapter();
configurer.init(auth);
configurer.configure(auth);
AuthenticationManager manager = configurer.getAuthenticationManagerBuilder()
.getOrBuild();
configurer.configureParent(auth);
return manager;
}
private boolean isAuthenticationManagerAlreadyConfigured(ApplicationContext context) {
return context.getBeanNamesForType(GlobalAuthenticationConfigurerAdapter.class).length > 2;
}
@Component
@ -142,8 +152,7 @@ public class AuthenticationManagerConfiguration extends
* methods are invoked before configure, which cannot be guaranteed at this point.</li>
* </ul>
*/
private class BootDefaultingAuthenticationConfigurerAdapter extends
GlobalAuthenticationConfigurerAdapter {
private class BootDefaultingAuthenticationConfigurerAdapter {
private AuthenticationManagerBuilder defaultAuth;
@ -159,7 +168,6 @@ public class AuthenticationManagerConfiguration extends
return this.defaultAuth;
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
if (auth.isConfigured()) {
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;
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.concurrent.atomic.AtomicReference;
import org.junit.After;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.TestAutoConfigurationPackage;
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.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.mock.web.MockServletContext;
import org.springframework.orm.jpa.JpaTransactionManager;
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.event.AuthenticationFailureBadCredentialsEvent;
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.AuthenticationException;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.SecurityFilterChain;
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}.
*
@ -138,7 +144,8 @@ public class SecurityAutoConfigurationTests {
catch (BadCredentialsException e) {
// expected
}
assertTrue(wrapper.get() instanceof AuthenticationFailureBadCredentialsEvent);
assertTrue("Wrong event type: " + wrapper.get(),
wrapper.get() instanceof AuthenticationFailureBadCredentialsEvent);
}
@Test
@ -154,6 +161,55 @@ public class SecurityAutoConfigurationTests {
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
public void testJpaCoexistsHappily() throws Exception {
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");
}
}
}