SEC-1195: Change <http> parsing behaviour to use an internal AuthenticationManager instance. Implemented "parent" AuthenticationManager in ProviderManager which is delegated to when no authentication is returned by the instances list of authentication providers. Extracted the Authentication success/failure publishing into a separate strategy.

This commit is contained in:
Luke Taylor 2009-07-15 01:28:28 +00:00
parent 1ca2e6e6fc
commit 6346e31517
9 changed files with 391 additions and 275 deletions

View File

@ -30,8 +30,6 @@ public class NamespaceAuthenticationManager extends ProviderManager implements B
public void afterPropertiesSet() throws Exception {
Assert.notNull(providerBeanNames, "provideBeanNames has not been set");
Assert.notEmpty(providerBeanNames, "No authentication providers were found in the application context");
super.afterPropertiesSet();
}
/**

View File

@ -106,7 +106,7 @@ public class MapBasedMethodSecurityMetadataSource extends AbstractFallbackMethod
* @param name type and method name, separated by a dot
* @param attr required authorities associated with the method
*/
public void addSecureMethod(String name, List<ConfigAttribute> attr) {
private void addSecureMethod(String name, List<ConfigAttribute> attr) {
int lastDotIndex = name.lastIndexOf(".");
if (lastDotIndex == -1) {

View File

@ -0,0 +1,17 @@
package org.springframework.security.authentication;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
/**
*
* @author Luke Taylor
* @version $Id$
* @since 3.0
*/
public interface AuthenticationEventPublisher {
void publishAuthenticationSuccess(Authentication authentication);
void publishAuthenticationFailure(AuthenticationException exception, Authentication authentication);
}

View File

@ -0,0 +1,134 @@
package org.springframework.security.authentication;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Properties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.security.authentication.concurrent.ConcurrentLoginException;
import org.springframework.security.authentication.event.AbstractAuthenticationEvent;
import org.springframework.security.authentication.event.AuthenticationFailureBadCredentialsEvent;
import org.springframework.security.authentication.event.AuthenticationFailureConcurrentLoginEvent;
import org.springframework.security.authentication.event.AuthenticationFailureCredentialsExpiredEvent;
import org.springframework.security.authentication.event.AuthenticationFailureDisabledEvent;
import org.springframework.security.authentication.event.AuthenticationFailureExpiredEvent;
import org.springframework.security.authentication.event.AuthenticationFailureLockedEvent;
import org.springframework.security.authentication.event.AuthenticationFailureProviderNotFoundEvent;
import org.springframework.security.authentication.event.AuthenticationFailureProxyUntrustedEvent;
import org.springframework.security.authentication.event.AuthenticationFailureServiceExceptionEvent;
import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.util.Assert;
/**
* The default strategy used by <tt>ProviderManager</tt> for publishing authentication events.
* <p>
* Maps well-known <tt>AuthenticationException</tt> types to events and publishes them via the
* application context. If configured as a bean, it will pick up the <tt>ApplicationEventPublisher</tt> automatically.
* Otherwise, the constructor which takes the publisher as an argument should be used.
* <p>
* The exception-mapping system can be fine-tuned by setting the <tt>additionalExceptionMappings</tt> as a
* <code>java.util.Properties</code> object. In the properties object, each of the keys represent the fully qualified
* classname of the exception, and each of the values represent the name of an event class which subclasses
* {@link org.springframework.security.authentication.event.AbstractAuthenticationFailureEvent}
* and provides its constructor. The <tt>additionalExceptionMappings</tt> will be merged with the default ones.
*
* @author Luke Taylor
* @version $Id$
* @since 3.0
*/
public class DefaultAuthenticationEventPublisher implements AuthenticationEventPublisher,
ApplicationEventPublisherAware {
private final Log logger = LogFactory.getLog(getClass());
private ApplicationEventPublisher applicationEventPublisher;
private final Properties exceptionMappings;
public DefaultAuthenticationEventPublisher() {
this(null);
}
public DefaultAuthenticationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
exceptionMappings = new Properties();
exceptionMappings.put(AccountExpiredException.class.getName(),
AuthenticationFailureExpiredEvent.class.getName());
exceptionMappings.put(AuthenticationServiceException.class.getName(),
AuthenticationFailureServiceExceptionEvent.class.getName());
exceptionMappings.put(LockedException.class.getName(),
AuthenticationFailureLockedEvent.class.getName());
exceptionMappings.put(CredentialsExpiredException.class.getName(),
AuthenticationFailureCredentialsExpiredEvent.class.getName());
exceptionMappings.put(DisabledException.class.getName(),
AuthenticationFailureDisabledEvent.class.getName());
exceptionMappings.put(BadCredentialsException.class.getName(),
AuthenticationFailureBadCredentialsEvent.class.getName());
exceptionMappings.put(UsernameNotFoundException.class.getName(),
AuthenticationFailureBadCredentialsEvent.class.getName());
exceptionMappings.put(ConcurrentLoginException.class.getName(),
AuthenticationFailureConcurrentLoginEvent.class.getName());
exceptionMappings.put(ProviderNotFoundException.class.getName(),
AuthenticationFailureProviderNotFoundEvent.class.getName());
exceptionMappings.put("org.springframework.security.authentication.cas.ProxyUntrustedException",
AuthenticationFailureProxyUntrustedEvent.class.getName());
}
public void publishAuthenticationSuccess(Authentication authentication) {
if (applicationEventPublisher != null) {
applicationEventPublisher.publishEvent(new AuthenticationSuccessEvent(authentication));
}
}
public void publishAuthenticationFailure(AuthenticationException exception,
Authentication authentication) {
String className = exceptionMappings.getProperty(exception.getClass().getName());
AbstractAuthenticationEvent event = null;
if (className != null) {
try {
Class<?> clazz = getClass().getClassLoader().loadClass(className);
Constructor<?> constructor = clazz.getConstructor(new Class[] {
Authentication.class, AuthenticationException.class
});
Object obj = constructor.newInstance(authentication, exception);
Assert.isInstanceOf(AbstractAuthenticationEvent.class, obj, "Must be an AbstractAuthenticationEvent");
event = (AbstractAuthenticationEvent) obj;
} catch (ClassNotFoundException ignored) {}
catch (NoSuchMethodException ignored) {}
catch (IllegalAccessException ignored) {}
catch (InstantiationException ignored) {}
catch (InvocationTargetException ignored) {}
}
if (event != null) {
if (applicationEventPublisher != null) {
applicationEventPublisher.publishEvent(event);
}
} else {
if (logger.isDebugEnabled()) {
logger.debug("No event was found for the exception " + exception.getClass().getName());
}
}
}
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
/**
* Sets additional exception to event mappings. These are automatically merged with the default
* exception to event mappings that <code>ProviderManager</code> defines.
*
* @param additionalExceptionMappings where keys are the fully-qualified string name of the exception class and the
* values are the fully-qualified string name of the event class to fire.
*/
public void setAdditionalExceptionMappings(Properties additionalExceptionMappings) {
Assert.notNull(additionalExceptionMappings, "The exceptionMappings object must not be null");
exceptionMappings.putAll(additionalExceptionMappings);
}
}

View File

@ -15,38 +15,18 @@
package org.springframework.security.authentication;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.Properties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.authentication.concurrent.ConcurrentLoginException;
import org.springframework.security.authentication.concurrent.ConcurrentSessionController;
import org.springframework.security.authentication.concurrent.NullConcurrentSessionController;
import org.springframework.security.authentication.event.AbstractAuthenticationEvent;
import org.springframework.security.authentication.event.AuthenticationFailureBadCredentialsEvent;
import org.springframework.security.authentication.event.AuthenticationFailureConcurrentLoginEvent;
import org.springframework.security.authentication.event.AuthenticationFailureCredentialsExpiredEvent;
import org.springframework.security.authentication.event.AuthenticationFailureDisabledEvent;
import org.springframework.security.authentication.event.AuthenticationFailureExpiredEvent;
import org.springframework.security.authentication.event.AuthenticationFailureLockedEvent;
import org.springframework.security.authentication.event.AuthenticationFailureProviderNotFoundEvent;
import org.springframework.security.authentication.event.AuthenticationFailureProxyUntrustedEvent;
import org.springframework.security.authentication.event.AuthenticationFailureServiceExceptionEvent;
import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.util.Assert;
@ -69,73 +49,41 @@ import org.springframework.util.Assert;
* concurrent session controller throws a {@link ConcurrentLoginException}. In both these cases, no further providers
* in the list will be queried.
*
* <h2>Event Publishing</h2>
* <p>
* If a valid <code>Authentication</code> is returned by an <code>AuthenticationProvider</code>, the
* <code>ProviderManager</code> will publish an
* {@link org.springframework.security.authentication.event.AuthenticationSuccessEvent}. If an
* <code>AuthenticationException</code> is detected, the final <code>AuthenticationException</code> thrown will be
* used to publish an appropriate failure event. By default <code>ProviderManager</code> maps common exceptions to
* events, but this can be fine-tuned by providing a new <code>exceptionMappings</code><code>java.util.Properties</code>
* object. In the properties object, each of the keys represent the fully qualified classname of the exception, and
* each of the values represent the name of an event class which subclasses
* {@link org.springframework.security.authentication.event.AbstractAuthenticationFailureEvent}
* and provides its constructor.
*
* Authentication event publishing is delegated to the configured {@link AuthenticationEventPublisher} which defaults
* to a null implementation which doesn't publish events, so if you are configuring the bean yourself you must inject
* a publisher bean if you want to receive events. The standard implementation is {@link DefaultAuthenticationEventPublisher}
* which maps common exceptions to events (in the case of authentication failure) and publishes an
* {@link org.springframework.security.authentication.event.AuthenticationSuccessEvent AuthenticationSuccessEvent} if
* authentication succeeds. If you are using the namespace then an instance of this bean will be used automatically by
* the <tt>&lt;http&gt;</tt> configuration, so you will receive events from the web part of your application automatically.
* <p>
* Note that the implementation also publishes authentication failure events when it obtains an authentication result
* (or an exception) from the "parent" <tt>AuthenticationManager</tt> if one has been set. So in this situation, the
* parent should not generally be configured to publish events or there will be duplicates.
*
* @author Ben Alex
* @author Luke Taylor
* @version $Id$
* @see ConcurrentSessionController
* @see DefaultAuthenticationEventPublisher
*/
public class ProviderManager extends AbstractAuthenticationManager implements InitializingBean, MessageSourceAware,
ApplicationEventPublisherAware {
public class ProviderManager extends AbstractAuthenticationManager implements MessageSourceAware {
//~ Static fields/initializers =====================================================================================
private static final Log logger = LogFactory.getLog(ProviderManager.class);
private static final Properties DEFAULT_EXCEPTION_MAPPINGS = new Properties();
//~ Instance fields ================================================================================================
private ApplicationEventPublisher applicationEventPublisher;
private AuthenticationEventPublisher eventPublisher = new NullEventPublisher();
private ConcurrentSessionController sessionController = new NullConcurrentSessionController();
private List<AuthenticationProvider> providers;
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
private Properties exceptionMappings = new Properties();
private Properties additionalExceptionMappings = new Properties();
static {
DEFAULT_EXCEPTION_MAPPINGS.put(AccountExpiredException.class.getName(),
AuthenticationFailureExpiredEvent.class.getName());
DEFAULT_EXCEPTION_MAPPINGS.put(AuthenticationServiceException.class.getName(),
AuthenticationFailureServiceExceptionEvent.class.getName());
DEFAULT_EXCEPTION_MAPPINGS.put(LockedException.class.getName(),
AuthenticationFailureLockedEvent.class.getName());
DEFAULT_EXCEPTION_MAPPINGS.put(CredentialsExpiredException.class.getName(),
AuthenticationFailureCredentialsExpiredEvent.class.getName());
DEFAULT_EXCEPTION_MAPPINGS.put(DisabledException.class.getName(),
AuthenticationFailureDisabledEvent.class.getName());
DEFAULT_EXCEPTION_MAPPINGS.put(BadCredentialsException.class.getName(),
AuthenticationFailureBadCredentialsEvent.class.getName());
DEFAULT_EXCEPTION_MAPPINGS.put(UsernameNotFoundException.class.getName(),
AuthenticationFailureBadCredentialsEvent.class.getName());
DEFAULT_EXCEPTION_MAPPINGS.put(ConcurrentLoginException.class.getName(),
AuthenticationFailureConcurrentLoginEvent.class.getName());
DEFAULT_EXCEPTION_MAPPINGS.put(ProviderNotFoundException.class.getName(),
AuthenticationFailureProviderNotFoundEvent.class.getName());
DEFAULT_EXCEPTION_MAPPINGS.put("org.springframework.security.authentication.cas.ProxyUntrustedException",
AuthenticationFailureProxyUntrustedEvent.class.getName());
}
public ProviderManager() {
exceptionMappings.putAll(DEFAULT_EXCEPTION_MAPPINGS);
}
private AuthenticationManager parent;
//~ Methods ========================================================================================================
public void afterPropertiesSet() throws Exception {
Assert.notNull(this.messages, "A message source must be set");
exceptionMappings.putAll(additionalExceptionMappings);
}
/**
* Attempts to authenticate the passed {@link Authentication} object.
* <p>
@ -157,6 +105,7 @@ public class ProviderManager extends AbstractAuthenticationManager implements In
public Authentication doAuthentication(Authentication authentication) throws AuthenticationException {
Class<? extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
Authentication result = null;
for (AuthenticationProvider provider : getProviders()) {
if (!provider.supports(toTest)) {
@ -165,81 +114,59 @@ public class ProviderManager extends AbstractAuthenticationManager implements In
logger.debug("Authentication attempt using " + provider.getClass().getName());
Authentication result;
try {
result = provider.authenticate(authentication);
if (result == null) {
continue;
if (result != null) {
copyDetails(authentication, result);
break;
}
} catch (AccountStatusException e) {
// SEC-546: Avoid polling additional providers if auth failure is due to invalid account status
lastException = e;
break;
eventPublisher.publishAuthenticationFailure(e, authentication);
throw e;
} catch (AuthenticationException e) {
lastException = e;
continue;
}
assert result != null;
copyDetails(authentication, result);
try {
sessionController.checkAuthenticationAllowed(result);
} catch (AuthenticationException e) {
// SEC-546: Avoid polling additional providers if concurrent login check fails
lastException = e;
break;
}
sessionController.registerSuccessfulAuthentication(result);
publishEvent(new AuthenticationSuccessEvent(result));
return result;
}
if (result == null && parent != null) {
// Allow the parent to try.
try {
result = parent.authenticate(authentication);
} catch (ProviderNotFoundException e) {
// ignore as we will throw below if no other exception occurred prior to calling parent and the parent
// may throw ProviderNotFound even though a provider in the child already handled the request
} catch (AuthenticationException e) {
lastException = e;
}
}
// Finally check if the concurrent session controller will allow authentication
try {
if (result != null) {
sessionController.checkAuthenticationAllowed(result);
sessionController.registerSuccessfulAuthentication(result);
eventPublisher.publishAuthenticationSuccess(result);
return result;
}
} catch (AuthenticationException e) {
lastException = e;
}
// Session control failed, parent was null, or didn't authenticate (or throw an exception).
if (lastException == null) {
lastException = new ProviderNotFoundException(messages.getMessage("ProviderManager.providerNotFound",
new Object[] {toTest.getName()}, "No AuthenticationProvider found for {0}"));
}
publishAuthenticationFailure(lastException, authentication);
eventPublisher.publishAuthenticationFailure(lastException, authentication);
throw lastException;
}
private void publishAuthenticationFailure(AuthenticationException exception, Authentication authentication) {
String className = exceptionMappings.getProperty(exception.getClass().getName());
AbstractAuthenticationEvent event = null;
if (className != null) {
try {
Class<?> clazz = getClass().getClassLoader().loadClass(className);
Constructor<?> constructor = clazz.getConstructor(new Class[] {
Authentication.class, AuthenticationException.class
});
Object obj = constructor.newInstance(authentication, exception);
Assert.isInstanceOf(AbstractAuthenticationEvent.class, obj, "Must be an AbstractAuthenticationEvent");
event = (AbstractAuthenticationEvent) obj;
} catch (ClassNotFoundException ignored) {}
catch (NoSuchMethodException ignored) {}
catch (IllegalAccessException ignored) {}
catch (InstantiationException ignored) {}
catch (InvocationTargetException ignored) {}
}
if (event != null) {
publishEvent(event);
} else {
if (logger.isDebugEnabled()) {
logger.debug("No event was found for the exception " + exception.getClass().getName());
}
}
}
/**
* Copies the authentication details from a source Authentication object to a destination one, provided the
* latter does not already have one set.
@ -273,14 +200,18 @@ public class ProviderManager extends AbstractAuthenticationManager implements In
return sessionController;
}
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
public void setMessageSource(MessageSource messageSource) {
this.messages = new MessageSourceAccessor(messageSource);
}
public void setParent(AuthenticationManager parent) {
this.parent = parent;
}
public void setAuthenticationEventPublisher(AuthenticationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
/**
* Sets the {@link AuthenticationProvider} objects to be used for authentication.
*
@ -302,7 +233,6 @@ public class ProviderManager extends AbstractAuthenticationManager implements In
/**
* Set the {@link ConcurrentSessionController} to be used for limiting users' sessions.
* The {@link NullConcurrentSessionController} is used by default.
*
* @param sessionController {@link ConcurrentSessionController}
*/
@ -310,20 +240,13 @@ public class ProviderManager extends AbstractAuthenticationManager implements In
this.sessionController = sessionController;
}
private void publishEvent(ApplicationEvent event) {
if (applicationEventPublisher != null) {
applicationEventPublisher.publishEvent(event);
}
private static final class NullEventPublisher implements AuthenticationEventPublisher {
public void publishAuthenticationFailure(AuthenticationException exception, Authentication authentication) {}
public void publishAuthenticationSuccess(Authentication authentication) {}
}
/**
* Sets additional exception to event mappings. These are automatically merged with the default
* exception to event mappings that <code>ProviderManager</code> defines.
*
* @param additionalExceptionMappings where keys are the fully-qualified string name of the exception class and the
* values are the fully-qualified string name of the event class to fire.
*/
public void setAdditionalExceptionMappings(Properties additionalExceptionMappings) {
this.additionalExceptionMappings = additionalExceptionMappings;
private static final class NullConcurrentSessionController implements ConcurrentSessionController {
public void checkAuthenticationAllowed(Authentication request) {}
public void registerSuccessfulAuthentication(Authentication authentication) {}
}
}

View File

@ -25,7 +25,7 @@ import org.springframework.security.core.AuthenticationException;
* @author Ben Alex
* @version $Id$
*/
public class NullConcurrentSessionController implements ConcurrentSessionController {
class NullConcurrentSessionController implements ConcurrentSessionController {
//~ Methods ========================================================================================================
public void checkAuthenticationAllowed(Authentication request)

View File

@ -1,32 +0,0 @@
package org.springframework.security;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEvent;
/**
* @author Luke Taylor
* @version $Id$
*/
public class MockApplicationEventPublisher implements ApplicationEventPublisher {
private Boolean expectedEvent;
private ApplicationEvent lastEvent;
public MockApplicationEventPublisher() {
}
public MockApplicationEventPublisher(boolean expectedEvent) {
this.expectedEvent = Boolean.valueOf(expectedEvent);
}
public void publishEvent(ApplicationEvent event) {
if (expectedEvent != null && !expectedEvent.booleanValue()) {
throw new IllegalStateException("The ApplicationEventPublisher did not expect to receive this event");
}
lastEvent = event;
}
public ApplicationEvent getLastEvent() {
return lastEvent;
}
}

View File

@ -23,7 +23,7 @@ import java.util.Arrays;
import java.util.List;
import org.junit.Test;
import org.springframework.security.MockApplicationEventPublisher;
import org.springframework.context.MessageSource;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.ProviderNotFoundException;
@ -31,7 +31,6 @@ import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.concurrent.ConcurrentLoginException;
import org.springframework.security.authentication.concurrent.ConcurrentSessionController;
import org.springframework.security.authentication.concurrent.NullConcurrentSessionController;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
@ -43,6 +42,7 @@ import org.springframework.security.core.authority.AuthorityUtils;
* @author Ben Alex
* @version $Id$
*/
@SuppressWarnings("unchecked")
public class ProviderManagerTests {
@Test(expected=ProviderNotFoundException.class)
@ -51,46 +51,34 @@ public class ProviderManagerTests {
AuthorityUtils.createAuthorityList("ROLE_ONE", "ROLE_TWO"));
ProviderManager mgr = makeProviderManager();
mgr.setApplicationEventPublisher(new MockApplicationEventPublisher(true));
mgr.setMessageSource(mock(MessageSource.class));
mgr.authenticate(token);
}
@Test
public void authenticationSucceedsWithSupportedTokenAndReturnsExpectedObject() throws Exception {
TestingAuthenticationToken token = new TestingAuthenticationToken("Test", "Password","ROLE_ONE","ROLE_TWO");
final Authentication a = mock(Authentication.class);
ProviderManager mgr = new ProviderManager();
AuthenticationEventPublisher publisher = mock(AuthenticationEventPublisher.class);
mgr.setAuthenticationEventPublisher(publisher);
mgr.setProviders(Arrays.asList(createProviderWhichReturns(a)));
ProviderManager mgr = makeProviderManager();
mgr.setApplicationEventPublisher(new MockApplicationEventPublisher(true));
Authentication result = mgr.authenticate(token);
if (!(result instanceof TestingAuthenticationToken)) {
fail("Should have returned instance of TestingAuthenticationToken");
}
TestingAuthenticationToken castResult = (TestingAuthenticationToken) result;
assertEquals("Test", castResult.getPrincipal());
assertEquals("Password", castResult.getCredentials());
assertEquals(AuthorityUtils.createAuthorityList("ROLE_ONE","ROLE_TWO"), castResult.getAuthorities());
Authentication result = mgr.authenticate(a);
assertEquals(a, result);
verify(publisher).publishAuthenticationSuccess(result);
}
@Test
public void authenticationSuccessWhenFirstProviderReturnsNullButSecondAuthenticates() {
TestingAuthenticationToken token = new TestingAuthenticationToken("Test", "Password","ROLE_ONE","ROLE_TWO");
ProviderManager mgr = makeProviderManagerWithMockProviderWhichReturnsNullInList();
mgr.setApplicationEventPublisher(new MockApplicationEventPublisher(true));
public void authenticationSucceedsWhenFirstProviderReturnsNullButSecondAuthenticates() {
final Authentication a = mock(Authentication.class);
ProviderManager mgr = new ProviderManager();
AuthenticationEventPublisher publisher = mock(AuthenticationEventPublisher.class);
mgr.setAuthenticationEventPublisher(publisher);
mgr.setProviders(Arrays.asList(createProviderWhichReturns(null), createProviderWhichReturns(a)));
Authentication result = mgr.authenticate(token);
if (!(result instanceof TestingAuthenticationToken)) {
fail("Should have returned instance of TestingAuthenticationToken");
}
TestingAuthenticationToken castResult = (TestingAuthenticationToken) result;
assertEquals("Test", castResult.getPrincipal());
assertEquals("Password", castResult.getCredentials());
assertEquals("ROLE_ONE", castResult.getAuthorities().get(0).getAuthority());
assertEquals("ROLE_TWO", castResult.getAuthorities().get(1).getAuthority());
Authentication result = mgr.authenticate(a);
assertSame(a, result);
verify(publisher).publishAuthenticationSuccess(result);
}
@Test
@ -99,11 +87,10 @@ public class ProviderManagerTests {
//The NullConcurrentSessionController should be the default
assertNotNull(target.getSessionController());
assertTrue(target.getSessionController() instanceof NullConcurrentSessionController);
ConcurrentSessionController csc = mock(ConcurrentSessionController.class);
target.setSessionController(csc);
assertEquals(csc, target.getSessionController());
assertSame(csc, target.getSessionController());
}
@Test(expected=IllegalArgumentException.class)
@ -119,8 +106,6 @@ public class ProviderManagerTests {
@Test(expected=IllegalArgumentException.class)
public void getProvidersFailsIfProviderListNotSet() throws Exception {
ProviderManager mgr = new ProviderManager();
mgr.afterPropertiesSet();
mgr.getProviders();
}
@ -137,6 +122,7 @@ public class ProviderManagerTests {
final Object resultDetails = "(Result Details)";
ProviderManager authMgr = makeProviderManager();
// A provider which sets the details object
AuthenticationProvider provider = new AuthenticationProvider() {
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
((TestingAuthenticationToken)authentication).setDetails(resultDetails);
@ -166,25 +152,53 @@ public class ProviderManagerTests {
request.setDetails(details);
Authentication result = authMgr.authenticate(request);
assertEquals(details, result.getDetails());
assertSame(details, result.getDetails());
}
@Test
public void authenticationExceptionIsIgnoredIfLaterProviderAuthenticates() throws Exception {
ProviderManager mgr = new ProviderManager();
final Authentication authReq = mock(Authentication.class);
mgr.setProviders(Arrays.asList(createProviderWhichThrows(new BadCredentialsException("")),
createProviderWhichReturns(authReq)));
assertSame(authReq, mgr.authenticate(mock(Authentication.class)));
}
@Test
public void authenticationExceptionIsRethrownIfNoLaterProviderAuthenticates() throws Exception {
ProviderManager mgr = new ProviderManager();
mgr.setProviders(Arrays.asList(createProviderWhichThrows(new BadCredentialsException("")),
createProviderWhichReturns(null)));
try {
mgr.authenticate(mock(Authentication.class));
fail("Expected BadCredentialsException");
} catch (BadCredentialsException expected) {
}
}
// SEC-546
@Test(expected=AccountStatusException.class)
@Test
public void accountStatusExceptionPreventsCallsToSubsequentProviders() throws Exception {
ProviderManager authMgr = makeProviderManager();
AuthenticationProvider iThrowAccountStatusException = createProviderWhichThrows(new AccountStatusException(""){});
AuthenticationProvider otherProvider = mock(AuthenticationProvider.class);
authMgr.setProviders(Arrays.asList(new MockProviderWhichThrowsAccountStatusException(),
new MockProviderWhichThrowsConcurrentLoginException()) );
authMgr.setProviders(Arrays.asList(iThrowAccountStatusException, otherProvider));
authMgr.authenticate(createAuthenticationToken());
try {
authMgr.authenticate(mock(Authentication.class));
fail("Expected AccountStatusException");
} catch (AccountStatusException expected) {
}
verifyZeroInteractions(otherProvider);
}
@Test(expected=ConcurrentLoginException.class)
public void concurrentLoginExceptionPreventsCallsToSubsequentProviders() throws Exception {
ProviderManager authMgr = makeProviderManager();
// Two providers so if the second is polled it will throw an AccountStatusException
authMgr.setProviders(Arrays.asList(new MockProvider(), new MockProviderWhichThrowsAccountStatusException()) );
// Two providers so if the second is polled it will throw an BadCredentialsException
authMgr.setProviders(Arrays.asList(new MockProvider(), createProviderWhichThrows(new BadCredentialsException(""))) );
TestingAuthenticationToken request = createAuthenticationToken();
ConcurrentSessionController ctrlr = mock(ConcurrentSessionController.class);
doThrow(new ConcurrentLoginException("mocked")).when(ctrlr).checkAuthenticationAllowed(request);
@ -193,6 +207,88 @@ public class ProviderManagerTests {
authMgr.authenticate(request);
}
@Test
public void parentAuthenticationIsUsedIfProvidersDontAuthenticate() throws Exception {
ProviderManager mgr = new ProviderManager();
mgr.setProviders(Arrays.asList(mock(AuthenticationProvider.class)));
Authentication authReq = mock(Authentication.class);
AuthenticationManager parent = mock(AuthenticationManager.class);
when(parent.authenticate(authReq)).thenReturn(authReq);
mgr.setParent(parent);
assertSame(authReq, mgr.authenticate(authReq));
}
@Test
public void parentIsNotCalledIfAccountStatusExceptionIsThrown() throws Exception {
ProviderManager mgr = new ProviderManager();
AuthenticationProvider iThrowAccountStatusException = createProviderWhichThrows(new AccountStatusException(""){});
mgr.setProviders(Arrays.asList(iThrowAccountStatusException));
AuthenticationManager parent = mock(AuthenticationManager.class);
mgr.setParent(parent);
try {
mgr.authenticate(mock(Authentication.class));
fail("Expected exception");
} catch (AccountStatusException expected) {
}
verifyZeroInteractions(parent);
}
@Test
public void providerNotFoundFromParentIsIgnored() throws Exception {
ProviderManager mgr = new ProviderManager();
final Authentication authReq = mock(Authentication.class);
AuthenticationEventPublisher publisher = mock(AuthenticationEventPublisher.class);
mgr.setAuthenticationEventPublisher(publisher);
// Set a provider that throws an exception - this is the exception we expect to be propagated
mgr.setProviders(Arrays.asList(createProviderWhichThrows(new BadCredentialsException(""))));
AuthenticationManager parent = mock(AuthenticationManager.class);
when(parent.authenticate(authReq)).thenThrow(new ProviderNotFoundException(""));
mgr.setParent(parent);
try {
mgr.authenticate(authReq);
fail("Expected exception");
} catch (BadCredentialsException expected) {
verify(publisher).publishAuthenticationFailure(expected, authReq);
}
}
@Test
public void authenticationExceptionFromParentOverridesPreviousOnes() throws Exception {
ProviderManager mgr = new ProviderManager();
final Authentication authReq = mock(Authentication.class);
AuthenticationEventPublisher publisher = mock(AuthenticationEventPublisher.class);
mgr.setAuthenticationEventPublisher(publisher);
// Set a provider that throws an exception - this is the exception we expect to be propagated
final BadCredentialsException expected = new BadCredentialsException("I'm the one from the parent");
mgr.setProviders(Arrays.asList(createProviderWhichThrows(new BadCredentialsException(""))));
AuthenticationManager parent = mock(AuthenticationManager.class);
when(parent.authenticate(authReq)).thenThrow(expected);
mgr.setParent(parent);
try {
mgr.authenticate(authReq);
fail("Expected exception");
} catch (BadCredentialsException e) {
assertSame(expected, e);
}
verify(publisher).publishAuthenticationFailure(expected, authReq);
}
private AuthenticationProvider createProviderWhichThrows(final AuthenticationException e) {
AuthenticationProvider provider = mock(AuthenticationProvider.class);
when(provider.supports(any(Class.class))).thenReturn(true);
when(provider.authenticate(any(Authentication.class))).thenThrow(e);
return provider;
}
private AuthenticationProvider createProviderWhichReturns(final Authentication a) {
AuthenticationProvider provider = mock(AuthenticationProvider.class);
when(provider.supports(any(Class.class))).thenReturn(true);
when(provider.authenticate(any(Authentication.class))).thenReturn(a);
return provider;
}
private TestingAuthenticationToken createAuthenticationToken() {
return new TestingAuthenticationToken("name", "password", new ArrayList<GrantedAuthority>(0));
}
@ -205,21 +301,6 @@ public class ProviderManagerTests {
ProviderManager mgr = new ProviderManager();
mgr.setProviders(providers);
mgr.afterPropertiesSet();
return mgr;
}
private ProviderManager makeProviderManagerWithMockProviderWhichReturnsNullInList() {
MockProviderWhichReturnsNull provider1 = new MockProviderWhichReturnsNull();
MockProvider provider2 = new MockProvider();
List<Object> providers = new ArrayList<Object>();
providers.add(provider1);
providers.add(provider2);
ProviderManager mgr = new ProviderManager();
mgr.setProviders(providers);
return mgr;
}
@ -242,43 +323,4 @@ public class ProviderManagerTests {
}
}
}
private class MockProviderWhichReturnsNull implements AuthenticationProvider {
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (supports(authentication.getClass())) {
return null;
} else {
throw new AuthenticationServiceException("Don't support this class");
}
}
public boolean supports(Class<? extends Object> authentication) {
if (TestingAuthenticationToken.class.isAssignableFrom(authentication)) {
return true;
} else {
return false;
}
}
}
private class MockProviderWhichThrowsAccountStatusException implements AuthenticationProvider {
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
throw new AccountStatusException("xxx") {};
}
public boolean supports(Class<? extends Object> authentication) {
return true;
}
}
private class MockProviderWhichThrowsConcurrentLoginException implements AuthenticationProvider {
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
throw new ConcurrentLoginException("xxx") {};
}
public boolean supports(Class<? extends Object> authentication) {
return true;
}
}
}

View File

@ -0,0 +1,34 @@
package org.springframework.security.util;
import static org.junit.Assert.*;
import org.aopalliance.intercept.MethodInvocation;
import org.junit.Test;
/**
*
* @author Luke Taylor
* @version $Id$
*/
public class MethodInvocationUtilsTests {
@Test
public void createFromClassReturnsMethodWithNoArgInfoForMethodWithNoArgs() {
MethodInvocation mi = MethodInvocationUtils.createFromClass(String.class, "length");
assertNotNull(mi);
}
@Test
public void createFromClassWithNoArgInfoReturnsNullForMethodWithArgs() {
MethodInvocation mi = MethodInvocationUtils.createFromClass(String.class, "codePointAt");
assertNull(mi);
}
@Test
public void createFromClassReturnsMethodIfGivArgInfoForMethodWithArgs() {
MethodInvocation mi = MethodInvocationUtils.createFromClass(null, String.class, "compareTo",
new Class<?>[]{String.class}, new Object[] {""});
assertNotNull(mi);
}
}