diff --git a/core/src/main/java/org/acegisecurity/providers/AuthenticationProvider.java b/core/src/main/java/org/acegisecurity/providers/AuthenticationProvider.java
index 4b5684bdf6..9eec8c15e9 100644
--- a/core/src/main/java/org/acegisecurity/providers/AuthenticationProvider.java
+++ b/core/src/main/java/org/acegisecurity/providers/AuthenticationProvider.java
@@ -35,7 +35,12 @@ public interface AuthenticationProvider {
*
* @param authentication the authentication request object.
*
- * @return a fully authenticated object including credentials.
+ * @return a fully authenticated object including credentials. May return
+ * null if the AuthenticationProvider is
+ * unable to support authentication of the passed
+ * Authentication object. In such a case, the next
+ * AuthenticationProvider that supports the presented
+ * Authentication class will be tried.
*
* @throws AuthenticationException if authentication fails.
*/
@@ -43,8 +48,18 @@ public interface AuthenticationProvider {
throws AuthenticationException;
/**
- * Returns true if this AuthenticationProvider supports the
- * indicated Authentication object.
+ * Returns true if this AuthenticationProvider
+ * supports the indicated Authentication object.
+ *
+ *
+ * Returning true does not guarantee an
+ * AuthenticationProvider will be able to authenticate the
+ * presented instance of the Authentication class. It simply
+ * indicates it can support closer evaluation of it. An
+ * AuthenticationProvider can still return null
+ * from the {@link #authenticate(Authentication)} method to indicate
+ * another AuthenticationProvider should be tried.
+ *
* Selection of an AuthenticationProvider capable of
@@ -52,7 +67,8 @@ public interface AuthenticationProvider {
* ProviderManager.
*
true if the implementation can more closely
+ * evaluate the Authentication class presented
*/
public boolean supports(Class authentication);
}
diff --git a/core/src/main/java/org/acegisecurity/providers/ProviderManager.java b/core/src/main/java/org/acegisecurity/providers/ProviderManager.java
index 290fb80b33..fa21ba0d6f 100644
--- a/core/src/main/java/org/acegisecurity/providers/ProviderManager.java
+++ b/core/src/main/java/org/acegisecurity/providers/ProviderManager.java
@@ -123,7 +123,11 @@ public class ProviderManager implements InitializingBean, AuthenticationManager
logger.debug("Authentication attempt using "
+ provider.getClass().getName());
- return provider.authenticate(authentication);
+ Authentication result = provider.authenticate(authentication);
+
+ if (result != null) {
+ return result;
+ }
}
}
diff --git a/core/src/test/java/org/acegisecurity/providers/ProviderManagerTests.java b/core/src/test/java/org/acegisecurity/providers/ProviderManagerTests.java
index 475f40130e..a8c8a119af 100644
--- a/core/src/test/java/org/acegisecurity/providers/ProviderManagerTests.java
+++ b/core/src/test/java/org/acegisecurity/providers/ProviderManagerTests.java
@@ -90,6 +90,26 @@ public class ProviderManagerTests extends TestCase {
assertEquals("ROLE_TWO", castResult.getAuthorities()[1].getAuthority());
}
+ public void testAuthenticationSuccessWhenFirstProviderReturnsNullButSecondAuthenticates() {
+ TestingAuthenticationToken token = new TestingAuthenticationToken("Test",
+ "Password",
+ new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
+ "ROLE_TWO")});
+
+ ProviderManager mgr = makeProviderManagerWithMockProviderWhichReturnsNullInList();
+ 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()[0].getAuthority());
+ assertEquals("ROLE_TWO", castResult.getAuthorities()[1].getAuthority());
+ }
+
public void testStartupFailsIfProviderListDoesNotContainingProviders()
throws Exception {
List providers = new Vector();
@@ -146,6 +166,19 @@ public class ProviderManagerTests extends TestCase {
return mgr;
}
+ private ProviderManager makeProviderManagerWithMockProviderWhichReturnsNullInList() {
+ MockProviderWhichReturnsNull provider1 = new MockProviderWhichReturnsNull();
+ MockProvider provider2 = new MockProvider();
+ List providers = new Vector();
+ providers.add(provider1);
+ providers.add(provider2);
+
+ ProviderManager mgr = new ProviderManager();
+ mgr.setProviders(providers);
+
+ return mgr;
+ }
+
//~ Inner Classes ==========================================================
private class MockProvider implements AuthenticationProvider {
@@ -168,4 +201,25 @@ public class ProviderManagerTests extends TestCase {
}
}
}
+
+ 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 authentication) {
+ if (TestingAuthenticationToken.class.isAssignableFrom(
+ authentication)) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+ }
}