diff --git a/core/src/main/java/org/springframework/ldap/core/AuthenticationErrorCallback.java b/core/src/main/java/org/springframework/ldap/core/AuthenticationErrorCallback.java
new file mode 100644
index 00000000..ebbde365
--- /dev/null
+++ b/core/src/main/java/org/springframework/ldap/core/AuthenticationErrorCallback.java
@@ -0,0 +1,20 @@
+package org.springframework.ldap.core;
+
+/**
+ * Callback interface to be used in the authentication methods in
+ * {@link LdapOperations} for performing operations when there
+ * are authentication errors. Can be useful when the cause of the
+ * authentication failure needs to be retrieved.
+ *
+ * @author Ulrik Sandberg
+ * @since 1.3.1
+ */
+public interface AuthenticationErrorCallback {
+ /**
+ * This method will be called with the authentication exception in
+ * case there is a problem with the authentication.
+ *
+ * @param e the exception that was caught in the authentication method
+ */
+ void execute(Exception e);
+}
diff --git a/core/src/main/java/org/springframework/ldap/core/LdapOperations.java b/core/src/main/java/org/springframework/ldap/core/LdapOperations.java
index d9a3d9dc..6c4b5b5b 100644
--- a/core/src/main/java/org/springframework/ldap/core/LdapOperations.java
+++ b/core/src/main/java/org/springframework/ldap/core/LdapOperations.java
@@ -1392,6 +1392,60 @@ public interface LdapOperations {
*/
boolean authenticate(String base, String filter, String password, AuthenticatedLdapEntryContextCallback callback);
+ /**
+ * Utility method to perform a simple LDAP 'bind' authentication. Search for
+ * the LDAP entry to authenticate using the supplied base DN and filter; use
+ * the DN of the found entry together with the password as input to
+ * {@link ContextSource#getContext(String, String)}, thus authenticating the
+ * entry. The resulting DirContext instance is then used as input to the
+ * supplied {@link AuthenticatedLdapEntryContextCallback} to perform any
+ * additional LDAP operations against the authenticated DirContext. If an
+ * exception is caught, the same exception is passed on to the given
+ * {@link AuthenticationErrorCallback}. This enables the caller to provide a
+ * callback that, for example, collects the exception for later processing.
+ *
+ * @param base the DN to use as the base of the search.
+ * @param filter the search filter - must result in a unique result.
+ * @param password the password to use for authentication.
+ * @param callback the callback that will be called to perform operations
+ * on the DirContext authenticated with the found user.
+ * @param errorCallback the callback that will be called if an exception is caught.
+ * @return true if the authentication was successful,
+ * false otherwise.
+ * @see #authenticate(Name, String, String, AuthenticatedLdapEntryContextCallback)
+ * @since 1.3.1
+ */
+ boolean authenticate(Name base, String filter, String password,
+ AuthenticatedLdapEntryContextCallback callback,
+ AuthenticationErrorCallback errorCallback);
+
+ /**
+ * Utility method to perform a simple LDAP 'bind' authentication. Search for
+ * the LDAP entry to authenticate using the supplied base DN and filter; use
+ * the DN of the found entry together with the password as input to
+ * {@link ContextSource#getContext(String, String)}, thus authenticating the
+ * entry. The resulting DirContext instance is then used as input to the
+ * supplied {@link AuthenticatedLdapEntryContextCallback} to perform any
+ * additional LDAP operations against the authenticated DirContext. If an
+ * exception is caught, the same exception is passed on to the given
+ * {@link AuthenticationErrorCallback}. This enables the caller to provide a
+ * callback that, for example, collects the exception for later processing.
+ *
+ * @param base the DN to use as the base of the search.
+ * @param filter the search filter - must result in a unique result.
+ * @param password the password to use for authentication.
+ * @param callback the callback that will be called to perform operations
+ * on the DirContext authenticated with the found user.
+ * @param errorCallback the callback that will be called if an exception is caught.
+ * @return true if the authentication was successful,
+ * false otherwise.
+ * @see #authenticate(String, String, String, AuthenticatedLdapEntryContextCallback)
+ * @since 1.3.1
+ */
+ boolean authenticate(String base, String filter, String password,
+ AuthenticatedLdapEntryContextCallback callback,
+ AuthenticationErrorCallback errorCallback);
+
/**
* Perform a search for a unique entry matching the specified search
* criteria and return the found object. If no entry is found or if there
diff --git a/core/src/main/java/org/springframework/ldap/core/LdapTemplate.java b/core/src/main/java/org/springframework/ldap/core/LdapTemplate.java
index c830cbc1..1f702a57 100644
--- a/core/src/main/java/org/springframework/ldap/core/LdapTemplate.java
+++ b/core/src/main/java/org/springframework/ldap/core/LdapTemplate.java
@@ -1383,7 +1383,9 @@ public class LdapTemplate implements LdapOperations, InitializingBean {
* .Name, java.lang.String, java.lang.String)
*/
public boolean authenticate(Name base, String filter, String password) {
- return authenticate(base, filter, password, new NullAuthenticatedLdapEntryContextCallback());
+ return authenticate(base, filter, password,
+ new NullAuthenticatedLdapEntryContextCallback(),
+ new NullAuthenticationErrorCallback());
}
/*
@@ -1394,7 +1396,9 @@ public class LdapTemplate implements LdapOperations, InitializingBean {
* , java.lang.String, java.lang.String)
*/
public boolean authenticate(String base, String filter, String password) {
- return authenticate(base, filter, password, new NullAuthenticatedLdapEntryContextCallback());
+ return authenticate(new DistinguishedName(base), filter, password,
+ new NullAuthenticatedLdapEntryContextCallback(),
+ new NullAuthenticationErrorCallback());
}
/*
@@ -1407,7 +1411,7 @@ public class LdapTemplate implements LdapOperations, InitializingBean {
*/
public boolean authenticate(String base, String filter, String password,
AuthenticatedLdapEntryContextCallback callback) {
- return authenticate(new DistinguishedName(base), filter, password, callback);
+ return authenticate(new DistinguishedName(base), filter, password, callback, new NullAuthenticationErrorCallback());
}
/*
@@ -1420,6 +1424,34 @@ public class LdapTemplate implements LdapOperations, InitializingBean {
*/
public boolean authenticate(Name base, String filter, String password,
final AuthenticatedLdapEntryContextCallback callback) {
+ return authenticate(base, filter, password, callback, new NullAuthenticationErrorCallback());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.springframework.ldap.core.LdapOperations#authenticate(java.lang.String
+ * , java.lang.String, java.lang.String,
+ * org.springframework.ldap.core.AuthenticatedLdapEntryContextCallback,
+ * org.springframework.ldap.core.AuthenticationErrorCallback)
+ */
+ public boolean authenticate(String base, String filter, String password,
+ final AuthenticatedLdapEntryContextCallback callback, final AuthenticationErrorCallback errorCallback) {
+ return authenticate(new DistinguishedName(base), filter, password, callback, errorCallback);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.springframework.ldap.core.LdapOperations#authenticate(javax.naming
+ * .Name, java.lang.String, java.lang.String,
+ * org.springframework.ldap.core.AuthenticatedLdapEntryContextCallback,
+ * org.springframework.ldap.core.AuthenticationErrorCallback)
+ */
+ public boolean authenticate(Name base, String filter, String password,
+ final AuthenticatedLdapEntryContextCallback callback, final AuthenticationErrorCallback errorCallback) {
List result = search(base, filter, new LdapEntryIdentificationContextMapper());
if (result.size() != 1) {
@@ -1442,6 +1474,7 @@ public class LdapTemplate implements LdapOperations, InitializingBean {
}
catch (Exception e) {
log.error("Authentication failed for entry with DN '" + entryIdentification.getAbsoluteDn() + "'", e);
+ errorCallback.execute(e);
return false;
}
}
@@ -1476,10 +1509,31 @@ public class LdapTemplate implements LdapOperations, InitializingBean {
return searchForObject(new DistinguishedName(base), filter, mapper);
}
- private static final class NullAuthenticatedLdapEntryContextCallback implements
- AuthenticatedLdapEntryContextCallback {
- public void executeWithContext(DirContext ctx, LdapEntryIdentification ldapEntryIdentification) {
+ public static final class NullAuthenticatedLdapEntryContextCallback
+ implements AuthenticatedLdapEntryContextCallback {
+ public void executeWithContext(DirContext ctx,
+ LdapEntryIdentification ldapEntryIdentification) {
// Do nothing
}
}
+
+ public static final class NullAuthenticationErrorCallback
+ implements AuthenticationErrorCallback {
+ public void execute(Exception e) {
+ // Do nothing
+ }
+ }
+
+ public static final class CollectingErrorCallback implements
+ AuthenticationErrorCallback {
+ private Exception error;
+
+ public void execute(Exception e) {
+ this.error = e;
+ }
+
+ public Exception getError() {
+ return error;
+ }
+ }
}
diff --git a/core/src/main/java/org/springframework/ldap/support/LdapUtils.java b/core/src/main/java/org/springframework/ldap/support/LdapUtils.java
index 20ee0174..4c712b51 100644
--- a/core/src/main/java/org/springframework/ldap/support/LdapUtils.java
+++ b/core/src/main/java/org/springframework/ldap/support/LdapUtils.java
@@ -401,7 +401,7 @@ public final class LdapUtils {
* href="http://blogs.msdn.com/oldnewthing/archive/2004/03/15/89753.aspx"
* >here.
*
- * @param sid SID in readable format
+ * @param string SID in readable format
* @return Binary version of the given sid
* @see LdapUtils#convertBinarySidToString(byte[])
* @since 1.3.1
diff --git a/test/integration-tests/src/test/java/org/springframework/ldap/LdapTemplateAuthenticationITest.java b/test/integration-tests/src/test/java/org/springframework/ldap/LdapTemplateAuthenticationITest.java
index 71fd4cf9..8b7a78c8 100644
--- a/test/integration-tests/src/test/java/org/springframework/ldap/LdapTemplateAuthenticationITest.java
+++ b/test/integration-tests/src/test/java/org/springframework/ldap/LdapTemplateAuthenticationITest.java
@@ -18,10 +18,14 @@ package org.springframework.ldap;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
+import static org.junit.Assert.assertNotNull;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.ldap.core.AuthenticatedLdapEntryContextCallback;
import org.springframework.ldap.core.LdapTemplate;
+import org.springframework.ldap.core.LdapTemplate.CollectingErrorCallback;
+import org.springframework.ldap.core.LdapTemplate.NullAuthenticatedLdapEntryContextCallback;
import org.springframework.ldap.filter.AndFilter;
import org.springframework.ldap.filter.EqualsFilter;
import org.springframework.ldap.filter.WhitespaceWildcardsFilter;
@@ -53,6 +57,19 @@ public class LdapTemplateAuthenticationITest extends AbstractLdapTemplateIntegra
assertFalse(tested.authenticate("", filter.toString(), "invalidpassword"));
}
+ @Test
+ public void testAuthenticateWithInvalidPasswordAndCollectedException() {
+ AndFilter filter = new AndFilter();
+ filter.and(new EqualsFilter("objectclass", "person")).and(new EqualsFilter("uid", "some.person3"));
+ final CollectingErrorCallback errorCallback = new CollectingErrorCallback();
+ final AuthenticatedLdapEntryContextCallback callback = new NullAuthenticatedLdapEntryContextCallback();
+ assertFalse(tested.authenticate("", filter.toString(), "invalidpassword", callback, errorCallback));
+ final Exception error = errorCallback.getError();
+ assertNotNull("collected error should not be null", error);
+ assertTrue("expected org.springframework.ldap.AuthenticationException", error instanceof AuthenticationException);
+ assertTrue("expected javax.naming.AuthenticationException", error.getCause() instanceof javax.naming.AuthenticationException);
+ }
+
@Test
public void testAuthenticateWithFilterThatDoesNotMatchAnything() {
AndFilter filter = new AndFilter();