LDAP-263: authentication methods for LdapQuery. Documentation updated to reflect fluent API changes.

This commit is contained in:
Mattias Hellborg Arthursson
2013-09-24 16:55:54 +02:00
parent 7134d21391
commit 27682b0b53
18 changed files with 591 additions and 558 deletions

View File

@@ -28,4 +28,8 @@ public class AuthenticationException extends NamingSecurityException {
public AuthenticationException(javax.naming.AuthenticationException cause) {
super(cause);
}
public AuthenticationException() {
this(null);
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2005-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ldap.core;
import javax.naming.directory.DirContext;
/**
* Callback interface to be used in the authentication methods in
* {@link LdapOperations} for performing operations on individually
* authenticated contexts.
*
* @author Mattias Hellborg Arthursson
* @since 2.0
*/
public interface AuthenticatedLdapEntryContextMapper<T> {
/**
* Perform some LDAP operation on the supplied authenticated
* <code>DirContext</code> instance. The target context will be
* automatically closed.
*
* @param ctx the <code>DirContext</code> instance to perform an operation
* on.
* @param ldapEntryIdentification the identification of the LDAP entry used
* to authenticate the supplied <code>DirContext</code>.
* @return the result of the operation, if any.
*/
T mapWithContext(DirContext ctx, LdapEntryIdentification ldapEntryIdentification);
}

View File

@@ -13,9 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ldap.core.support;
import org.springframework.ldap.core.AuthenticationErrorCallback;
package org.springframework.ldap.core;
/**
* Convenience implementation of AuthenticationErrorCallback that stores the
@@ -47,4 +45,13 @@ public final class CollectingAuthenticationErrorCallback implements Authenticati
public Exception getError() {
return error;
}
/**
* Check whether this callback has collected an error.
*
* @return <code>true</code> if an error has been collected, <code>false</code> otherwise.
*/
public boolean hasError() {
return error != null;
}
}

View File

@@ -27,7 +27,7 @@ import java.util.List;
* @author Mattias Hellborg Arthursson
* @since 1.3.2
* @see <a href="http://www.watersprings.org/pub/id/draft-kashi-incremental-00.txt">Incremental Retrieval of Multi-valued Properties</a>
* @see {@link org.springframework.ldap.core.support.DefaultIncrementalAttributesMapper}
* @see org.springframework.ldap.core.support.DefaultIncrementalAttributesMapper
*/
public interface IncrementalAttributesMapper<T extends IncrementalAttributesMapper> extends AttributesMapper<T> {
/**

View File

@@ -1323,10 +1323,12 @@ public interface LdapOperations {
* @return <code>true</code> if the authentication was successful,
* <code>false</code> otherwise.
* @since 1.3
* @deprecated use {@link #authenticate(org.springframework.ldap.query.LdapQuery, String)}
* or {@link #authenticate(org.springframework.ldap.query.LdapQuery, String, AuthenticatedLdapEntryContextMapper)}
*/
boolean authenticate(Name base, String filter, String password);
/**
/**
* 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
@@ -1347,6 +1349,8 @@ public interface LdapOperations {
* @return <code>true</code> if the authentication was successful,
* <code>false</code> otherwise.
* @since 1.3
* @deprecated use {@link #authenticate(org.springframework.ldap.query.LdapQuery, String)}
* or {@link #authenticate(org.springframework.ldap.query.LdapQuery, String, AuthenticatedLdapEntryContextMapper)}
*/
boolean authenticate(String base, String filter, String password);
@@ -1368,6 +1372,8 @@ public interface LdapOperations {
* <code>false</code> otherwise.
* @see #authenticate(Name, String, String)
* @since 1.3
* @deprecated use {@link #authenticate(org.springframework.ldap.query.LdapQuery, String)}
* or {@link #authenticate(org.springframework.ldap.query.LdapQuery, String, AuthenticatedLdapEntryContextMapper)}
*/
boolean authenticate(Name base, String filter, String password, AuthenticatedLdapEntryContextCallback callback);
@@ -1389,6 +1395,8 @@ public interface LdapOperations {
* <code>false</code> otherwise.
* @see #authenticate(String, String, String)
* @since 1.3
* @deprecated use {@link #authenticate(org.springframework.ldap.query.LdapQuery, String)}
* or {@link #authenticate(org.springframework.ldap.query.LdapQuery, String, AuthenticatedLdapEntryContextMapper)}
*/
boolean authenticate(String base, String filter, String password, AuthenticatedLdapEntryContextCallback callback);
@@ -1414,6 +1422,8 @@ public interface LdapOperations {
* <code>false</code> otherwise.
* @see #authenticate(Name, String, String, AuthenticatedLdapEntryContextCallback)
* @since 1.3.1
* @deprecated use {@link #authenticate(org.springframework.ldap.query.LdapQuery, String)}
* or {@link #authenticate(org.springframework.ldap.query.LdapQuery, String, AuthenticatedLdapEntryContextMapper)}
*/
boolean authenticate(Name base, String filter, String password,
AuthenticatedLdapEntryContextCallback callback,
@@ -1441,29 +1451,33 @@ public interface LdapOperations {
* <code>false</code> otherwise.
* @see #authenticate(String, String, String, AuthenticatedLdapEntryContextCallback)
* @since 1.3.1
* @deprecated use {@link #authenticate(org.springframework.ldap.query.LdapQuery, String)}
* or {@link #authenticate(org.springframework.ldap.query.LdapQuery, String, AuthenticatedLdapEntryContextMapper)}
*/
boolean authenticate(String 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. 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 errorCallback the callback that will be called if an exception is caught.
* @return <code>true</code> if the authentication was successful,
* <code>false</code> otherwise.
* @see #authenticate(Name, String, String, AuthenticatedLdapEntryContextCallback, AuthenticationErrorCallback)
* @since 1.3.1
*/
/**
* 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. 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 errorCallback the callback that will be called if an exception is caught.
* @return <code>true</code> if the authentication was successful,
* <code>false</code> otherwise.
* @see #authenticate(Name, String, String, AuthenticatedLdapEntryContextCallback, AuthenticationErrorCallback)
* @since 1.3.1
* @deprecated use {@link #authenticate(org.springframework.ldap.query.LdapQuery, String)}
* or {@link #authenticate(org.springframework.ldap.query.LdapQuery, String, AuthenticatedLdapEntryContextMapper)}
*/
boolean authenticate(Name base, String filter, String password,
AuthenticationErrorCallback errorCallback);
@@ -1483,12 +1497,64 @@ public interface LdapOperations {
* @return <code>true</code> if the authentication was successful,
* <code>false</code> otherwise.
* @throws IncorrectResultSizeDataAccessException if more than one users were found
* @see #authenticate(String, String, String, AuthenticatedLdapEntryContextCallback, AuthenticationErrorCallback)
* @since 1.3.1
* @deprecated use {@link #authenticate(org.springframework.ldap.query.LdapQuery, String)}
* or {@link #authenticate(org.springframework.ldap.query.LdapQuery, String, AuthenticatedLdapEntryContextMapper)}
*/
boolean authenticate(String base, String filter, String password,
AuthenticationErrorCallback errorCallback);
/**
* Utility method to perform a simple LDAP 'bind' authentication. Search for
* the LDAP entry to authenticate using the supplied LdapQuery; use
* the DN of the found entry together with the password as input to
* {@link ContextSource#getContext(String, String)}, thus authenticating the
* entry.
* <p>
* <b>Note:</b> This method differs from the older authenticate methods in that encountered
* exceptions are thrown rather than supplied to a callback for handling.
* </p>
*
* @param query the LdapQuery specifying the details of the search.
* @param password the password to use for authentication.
* @param mapper the callback that will be called to perform operations
* on the DirContext authenticated with the found user.
* <code>false</code> otherwise.
* @return the result from the callback.
* @throws IncorrectResultSizeDataAccessException if more than one users were found
* @throws org.springframework.dao.EmptyResultDataAccessException if only one user was found
* @throws NamingException if something went wrong in authentication.
*
* @since 2.0
*/
<T> T authenticate(LdapQuery query, String password, AuthenticatedLdapEntryContextMapper<T> mapper);
/**
* 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. 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.
* <p>
* <b>Note:</b> This method differs from the older authenticate methods in that encountered
* exceptions are thrown rather than supplied to a callback for handling.
* </p>
*
* @param query the LdapQuery specifying the details of the search.
* @param password the password to use for authentication.
* <code>false</code> otherwise.
* @throws IncorrectResultSizeDataAccessException if more than one users were found
* @throws org.springframework.dao.EmptyResultDataAccessException if only one user was found
* @throws NamingException if something went wrong in authentication.
*
* @since 2.0
*/
void authenticate(LdapQuery query, String password);
/**
* 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

View File

@@ -20,7 +20,9 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.ldap.AuthenticationException;
import org.springframework.ldap.NamingException;
import org.springframework.ldap.UncategorizedLdapException;
import org.springframework.ldap.query.LdapQuery;
import org.springframework.ldap.support.LdapUtils;
import org.springframework.util.Assert;
@@ -1491,42 +1493,94 @@ public class LdapTemplate implements LdapOperations, InitializingBean {
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() == 0) {
String msg = "No results found for search, base: '" + base + "'; filter: '" + filter + "'.";
log.info(msg);
return false;
} else if (result.size() > 1) {
String msg = "base: '" + base + "'; filter: '" + filter + "'.";
throw new IncorrectResultSizeDataAccessException(msg, 1, result.size());
}
final LdapEntryIdentification entryIdentification = (LdapEntryIdentification) result.get(0);
try {
DirContext ctx = contextSource.getContext(entryIdentification.getAbsoluteDn().toString(), password);
executeWithContext(new ContextExecutor<Object>() {
public Object executeWithContext(DirContext ctx) throws javax.naming.NamingException {
callback.executeWithContext(ctx, entryIdentification);
return null;
}
}, ctx);
return true;
}
catch (Exception e) {
log.info("Authentication failed for entry with DN '" + entryIdentification.getAbsoluteDn() + "'", e);
errorCallback.execute(e);
return false;
}
return authenticate(base,
filter,
password,
getDefaultSearchControls(defaultSearchScope, RETURN_OBJ_FLAG, null),
callback,
errorCallback);
}
/*
* (non-Javadoc)
*
* @see
* org.springframework.ldap.core.LdapOperations#searchForObject(javax.naming
* .Name, java.lang.String, org.springframework.ldap.core.ContextMapper)
*/
private boolean authenticate(Name base,
String filter,
String password,
SearchControls searchControls,
final AuthenticatedLdapEntryContextCallback callback,
final AuthenticationErrorCallback errorCallback) {
List<LdapEntryIdentification> result = search(base, filter, searchControls, new LdapEntryIdentificationContextMapper());
if (result.size() == 0) {
String msg = "No results found for search, base: '" + base + "'; filter: '" + filter + "'.";
log.info(msg);
return false;
} else if (result.size() > 1) {
String msg = "base: '" + base + "'; filter: '" + filter + "'.";
throw new IncorrectResultSizeDataAccessException(msg, 1, result.size());
}
final LdapEntryIdentification entryIdentification = result.get(0);
try {
DirContext ctx = contextSource.getContext(entryIdentification.getAbsoluteName().toString(), password);
executeWithContext(new ContextExecutor<Object>() {
public Object executeWithContext(DirContext ctx) throws javax.naming.NamingException {
callback.executeWithContext(ctx, entryIdentification);
return null;
}
}, ctx);
return true;
}
catch (Exception e) {
log.info("Authentication failed for entry with DN '" + entryIdentification.getAbsoluteName() + "'", e);
errorCallback.execute(e);
return false;
}
}
@Override
public <T> T authenticate(LdapQuery query, String password, AuthenticatedLdapEntryContextMapper<T> mapper) {
SearchControls searchControls = searchControlsForQuery(query, RETURN_OBJ_FLAG);
ReturningAuthenticatedLdapEntryContext<T> mapperCallback =
new ReturningAuthenticatedLdapEntryContext<T>(mapper);
CollectingAuthenticationErrorCallback errorCallback =
new CollectingAuthenticationErrorCallback();
boolean succeeded = authenticate(query.base(),
query.filter().encode(),
password,
searchControls,
mapperCallback,
errorCallback);
if(errorCallback.hasError()) {
Exception error = errorCallback.getError();
if (error instanceof NamingException) {
throw (NamingException) error;
} else {
throw new UncategorizedLdapException(error);
}
} else if(!succeeded) {
throw new AuthenticationException();
}
return mapperCallback.collectedObject;
}
@Override
public void authenticate(LdapQuery query, String password) {
authenticate(query,
password,
new NullAuthenticatedLdapEntryContextCallback());
}
/*
* (non-Javadoc)
*
* @see
* org.springframework.ldap.core.LdapOperations#searchForObject(javax.naming
* .Name, java.lang.String, org.springframework.ldap.core.ContextMapper)
*/
public <T> T searchForObject(Name base, String filter, ContextMapper<T> mapper) {
return searchForObject(base,
filter,
@@ -1564,12 +1618,17 @@ public class LdapTemplate implements LdapOperations, InitializingBean {
}
private static final class NullAuthenticatedLdapEntryContextCallback
implements AuthenticatedLdapEntryContextCallback {
implements AuthenticatedLdapEntryContextCallback, AuthenticatedLdapEntryContextMapper<Object>{
public void executeWithContext(DirContext ctx,
LdapEntryIdentification ldapEntryIdentification) {
// Do nothing
}
}
@Override
public Object mapWithContext(DirContext ctx, LdapEntryIdentification ldapEntryIdentification) {
return null;
}
}
private static final class NullAuthenticationErrorCallback
implements AuthenticationErrorCallback {
@@ -1578,6 +1637,22 @@ public class LdapTemplate implements LdapOperations, InitializingBean {
}
}
private static final class ReturningAuthenticatedLdapEntryContext<T>
implements AuthenticatedLdapEntryContextCallback {
private final AuthenticatedLdapEntryContextMapper<T> mapper;
private T collectedObject;
private ReturningAuthenticatedLdapEntryContext(AuthenticatedLdapEntryContextMapper<T> mapper) {
this.mapper = mapper;
}
@Override
public void executeWithContext(DirContext ctx, LdapEntryIdentification ldapEntryIdentification) {
collectedObject = mapper.mapWithContext(ctx, ldapEntryIdentification);
}
}
@Override
public <T> List<T> search(LdapQuery query, ContextMapper<T> mapper) {
SearchControls searchControls = searchControlsForQuery(query, RETURN_OBJ_FLAG);

View File

@@ -17,6 +17,8 @@
package org.springframework.ldap.core.support;
import org.springframework.ldap.core.AuthenticatedLdapEntryContextCallback;
import org.springframework.ldap.core.AuthenticatedLdapEntryContextMapper;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.LdapEntryIdentification;
import org.springframework.ldap.support.LdapUtils;
@@ -27,19 +29,29 @@ import javax.naming.directory.DirContext;
* Attempts to perform an LDAP operation in the authenticated context, because
* Active Directory might allow bind with incorrect password (specifically empty
* password), and later refuse operations. We want to fail fast when
* authenticating.
* authenticating. {@link #mapWithContext(javax.naming.directory.DirContext, org.springframework.ldap.core.LdapEntryIdentification)}
* returns the {@link DirContextOperations} instance that results from the lookup operation. This instance
* can be used to obtain information regarding the authenticated user.
*
* @author Hugo Josefson
* @author Mattias Hellborg Arthursson
* @since 1.3.1
*/
public class LookupAttemptingCallback implements AuthenticatedLdapEntryContextCallback {
public class LookupAttemptingCallback implements
AuthenticatedLdapEntryContextCallback, AuthenticatedLdapEntryContextMapper<DirContextOperations> {
@Override
public void executeWithContext(DirContext ctx, LdapEntryIdentification ldapEntryIdentification) {
try {
ctx.lookup(ldapEntryIdentification.getRelativeName());
}
catch (NamingException e) {
// rethrow, because we aren't allowed to throw checked exceptions.
throw LdapUtils.convertLdapException(e);
}
mapWithContext(ctx, ldapEntryIdentification);
}
@Override
public DirContextOperations mapWithContext(DirContext ctx, LdapEntryIdentification ldapEntryIdentification) {
try {
return (DirContextOperations) ctx.lookup(ldapEntryIdentification.getRelativeName());
}
catch (NamingException e) {
// rethrow, because we aren't allowed to throw checked exceptions.
throw LdapUtils.convertLdapException(e);
}
}
}

View File

@@ -17,7 +17,7 @@
package org.springframework.ldap.query;
/**
* Builder for and/or filters.
* And/or filter builder support for LdapQuery.
*
* @author Mattias Hellborg Arthursson
* @since 2.0

View File

@@ -94,8 +94,8 @@ class DefaultContainerCriteria implements ContainerCriteria {
@Override
public Filter filter() {
if(filters.size() == 1) {
// No need to wrap in And/OrFilter if there's just one condition.
return filters.iterator().next();
}

View File

@@ -126,4 +126,8 @@ public class LdapQueryBuilderTest {
query.base("dc=261consulting,dc=com");
}
@Test(expected = IllegalStateException.class)
public void verifyThatOperatorChangeIsIllegal() {
query().where("cn").is("John Doe").and("sn").is("Doe").or("objectclass").is("person");
}
}

View File

@@ -32,6 +32,8 @@ import javax.naming.directory.Attributes;
import javax.naming.ldap.LdapName;
import java.util.List;
import static org.springframework.ldap.query.LdapQueryBuilder.query;
/**
* Default implementation of PersonDao. This implementation uses
* DirContextAdapter for managing attribute values. We use a ContextMapper
@@ -84,8 +86,9 @@ public class PersonDaoImpl implements PersonDao {
@Override
public List<Person> findAll() {
EqualsFilter filter = new EqualsFilter("objectclass", "person");
return ldapTemplate.search(LdapUtils.emptyLdapName(), filter.encode(), PERSON_CONTEXT_MAPPER);
return ldapTemplate.search(query()
.where("objectclass").is("person"),
PERSON_CONTEXT_MAPPER);
}
@Override

View File

@@ -0,0 +1,111 @@
<?xml version="1.0" encoding="UTF-8"?>
<chapter id="query-builder-advanced">
<title>Advanced LDAP Queries</title>
<sect1>
<title>LDAP Query Builder Parameters</title>
<para>The <literal>LdapQueryBuilder</literal> and its associated classes is intended to support all parameters
that can be supplied to an LDAP search. The following parameters are supported:
<itemizedlist>
<listitem><literal>base</literal> - specifies the root DN in the LDAP tree where the search should start.</listitem>
<listitem><literal>searchScope</literal> - specifies how deep into the LDAP tree the search should traverse.</listitem>
<listitem><literal>attributes</literal> - specifies the attributes to return from the search. Default is all.</listitem>
<listitem><literal>countLimit</literal> - specifies the maximum number of entries to return from the search.</listitem>
<listitem><literal>timeLimit</literal> - specifies the maximum time that the search may take.</listitem>
<listitem>Search filter - the conditions that the entries we are looking for must meet.</listitem>
</itemizedlist>
</para>
<para>
An <literal>LdapQueryBuilder</literal> is created with a call to the <literal>query</literal> method of
<literal>LdapQueryBuilder</literal>. It's intended as a fluent builder API, where the base parameters are defined first,
followed by the filter specification calls. Once filter conditions have been started to be defined with a call to the
<literal>where</literal> method of <literal>LdapQueryBuilder</literal>, later attempts to call e.g. <literal>base</literal>
will be rejected. The base search parameters are optional, but at least one filter specification call is required.
</para>
<example>
<title>Search for all entries with objectclass person</title>
<programlisting>import static org.springframework.ldap.query.LdapQueryBuilder.query;
...
List&lt;Person&gt; persons = ldapTemplate.search(
query().where("objectclass").is("person"),
new PersonAttributesMapper());
</programlisting>
</example>
<example>
<title>Search for all entries with objectclass person and cn=John Doe</title>
<programlisting>import static org.springframework.ldap.query.LdapQueryBuilder.query;
...
List&lt;Person&gt; persons = ldapTemplate.search(
query().where("objectclass").is("person")
.and("cn").is("John Doe"),
new PersonAttributesMapper());
</programlisting>
</example>
<example>
<title>Search for all entries with objectclass person starting at <literal>dc=261consulting,dc=com</literal></title>
<programlisting>import static org.springframework.ldap.query.LdapQueryBuilder.query;
...
List&lt;Person&gt; persons = ldapTemplate.search(
query().base("dc=261consulting,dc=com")
.where("objectclass").is("person"),
new PersonAttributesMapper());
</programlisting>
</example>
<example>
<title>Search for all entries with objectclass person starting at <literal>dc=261consulting,dc=com</literal>,
only returning the cn attribute</title>
<programlisting>import static org.springframework.ldap.query.LdapQueryBuilder.query;
...
List&lt;Person&gt; persons = ldapTemplate.search(
query().base("dc=261consulting,dc=com")
.attributes("cn")
.where("objectclass").is("person"),
new PersonAttributesMapper());
</programlisting>
</example>
<example>
<title>Search for all entries with objectclass person where sn=Doe or Doo (nested query)</title>
<programlisting>import static org.springframework.ldap.query.LdapQueryBuilder.query;
...
List&lt;Person&gt; persons = ldapTemplate.search(
query().where("objectclass").is("person"),
.and(query().where("cn").is("Doe").or("cn").is("Doo));
new PersonAttributesMapper());
</programlisting>
</example>
</sect1>
<sect1>
<title>Filter Criteria</title>
<para>
The examples above demonstrates simple equals conditions in LDAP filters. The LDAP query builder has support
for the following criteria types:
<itemizedlist>
<listitem><literal>is</literal> - specifies an equals condition (=).</listitem>
<listitem><literal>gte</literal> - specifies a greater than or equals condition (&gt;=).</listitem>
<listitem><literal>lte</literal> - specifies a less than or equals condition (&lt;=).</listitem>
<listitem>
<literal>like</literal> - specifies a &quot;like&quot; condition where wildcards can be included in the query,
e.g. <literal>where("cn").like("J*hn Doe")</literal> will result int the filter <literal>(cn=J*hn Doe)</literal>.
</listitem>
<listitem>
<literal>whitespaceWildcardsLike</literal> - specifies a condition where all whitespace is replaced with wildcards,
e.g. <literal>where("cn").whitespaceWildcardsLike("John Doe")</literal> will result in the filter
<literal>(cn=*John*Doe*)</literal>.
</listitem>
<listitem>
<literal>isPresent</literal> - specifies condition that checks for the presence of an attribute,
e.g. <literal>where("cn").isPresent()</literal> will result in the filter <literal>(cn=*)</literal>.
</listitem>
<listitem>
<literal>not</literal> - specifies that the current condition should be negated, e.g.
<literal>where("sn").not().is("Doe)</literal> will result in the filter <literal>(!(sn=Doe))</literal>
</listitem>
</itemizedlist>
</para>
</sect1>
</chapter>

View File

@@ -12,6 +12,7 @@
<title>AttributesMapper that returns a single attribute</title>
<programlisting>package com.example.dao;
import static org.springframework.ldap.query.LdapQueryBuilder.query;
public class PersonDaoImpl implements PersonDao {
private LdapTemplate ldapTemplate;
@@ -20,13 +21,13 @@ public class PersonDaoImpl implements PersonDao {
this.ldapTemplate = ldapTemplate;
}
public List getAllPersonNames() {
return ldapTemplate.search(
"", "(objectclass=person)",
<emphasis role="bold"> new AttributesMapper() {
public Object mapFromAttributes(Attributes attrs)
public List&lt;String&gt; getAllPersonNames() {
return ldapTemplate.search(query()
.where("objectclass").is("person"),
<emphasis role="bold"> new AttributesMapper&lt;String&gt;() {
public String mapFromAttributes(Attributes attrs)
throws NamingException {
return attrs.get("cn").get();
return (String) attrs.get("cn").get();
}
}</emphasis>);
}
@@ -49,12 +50,13 @@ public class PersonDaoImpl implements PersonDao {
<title>AttributesMapper that returns a Person object</title>
<programlisting>package com.example.dao;
import static org.springframework.ldap.query.LdapQueryBuilder.query;
public class PersonDaoImpl implements PersonDao {
private LdapTemplate ldapTemplate;
...
<emphasis role="bold"> private class PersonAttributesMapper implements AttributesMapper {
public Object mapFromAttributes(Attributes attrs) throws NamingException {
<emphasis role="bold"> private class PersonAttributesMapper implements AttributesMapper&lt;Person&gt; {
public Person mapFromAttributes(Attributes attrs) throws NamingException {
Person person = new Person();
person.setFullName((String)attrs.get("cn").get());
person.setLastName((String)attrs.get("sn").get());
@@ -63,8 +65,9 @@ public class PersonDaoImpl implements PersonDao {
}
}
</emphasis>
public List getAllPersons() {
return ldapTemplate.search("", "(objectclass=person)", <emphasis
public List&lt;Person&gt; getAllPersons() {
return ldapTemplate.search(query()
.where("objectclass").is("person"), <emphasis
role="bold">new PersonAttributesMapper()</emphasis>);
}
}</programlisting>
@@ -85,7 +88,7 @@ public class PersonDaoImpl implements PersonDao {
private LdapTemplate ldapTemplate;
...
public Person findPerson(String dn) {
return (Person) ldapTemplate.lookup(dn, new PersonAttributesMapper());
return ldapTemplate.lookup(dn, new PersonAttributesMapper());
}
}</programlisting>
</example>
@@ -95,31 +98,37 @@ public class PersonDaoImpl implements PersonDao {
this case resulting in a <literal>Person</literal> object.</para>
</sect1>
<sect1 id="basic-filters">
<title>Building Dynamic Filters</title>
<para>We can build dynamic filters to use in searches, using the classes
from the <literal>org.springframework.ldap.filter</literal>
package. Let's say that we want the following filter:
<sect1 id="basic-queries">
<title>Building LDAP Queries</title>
<para>LDAP searches involve a number of parameters, e.g. Base LDAP path,
search scope, attributes to return, and search filters.</para>
<para>Spring LDAP provides an <literal>LdapQueryBuilder</literal> with a fluent
API for building LDAP Queries.</para>
<para>Let's say that we want to perform a search starting at the
base DN <literal>dc=261consulting,dc=com</literal>, limiting the returned attributes to &quot;cn&quot;
and &quot;sn&quot;, with the following filter:
<literal>(&amp;(objectclass=person)(sn=?))</literal>, where we want the
<literal>?</literal> to be replaced with the value of the parameter
<literal>lastName</literal>. This is how we do it using the filter support
classes:</para>
<literal>lastName</literal>. This is how we do it using the LdapQueryBuilder:</para>
<example>
<title>Building a search filter dynamically</title>
<programlisting>package com.example.dao;
import static org.springframework.ldap.query.LdapQueryBuilder.query;
public class PersonDaoImpl implements PersonDao {
private LdapTemplate ldapTemplate;
...
public List getPersonNamesByLastName(String lastName) {
<emphasis role="bold"> AndFilter filter = new AndFilter();
filter.and(new EqualsFilter("objectclass", "person"));
filter.and(new EqualsFilter("sn", lastName));
</emphasis> return ldapTemplate.search(
"", <emphasis role="bold">filter.encode()</emphasis>,
<emphasis role="bold">
LdapQuery query = query()
.base("dc=261consulting,dc=com")
.attributes("cn", "sn")
.where("objectclass").is("person")
.and("sn").is(lastName);
</emphasis>
return ldapTemplate.search(query,
new AttributesMapper() {
public Object mapFromAttributes(Attributes attrs)
throws NamingException {
@@ -129,30 +138,31 @@ public class PersonDaoImpl implements PersonDao {
}
}</programlisting>
</example>
<para>To perform a wildcard search, it's possible to use the
<literal>WhitespaceWildcardsFilter</literal>:</para>
<example>
<title>Building a wildcard search filter</title>
<programlisting>AndFilter filter = new AndFilter();
filter.and(new EqualsFilter("objectclass", "person"));
filter.and(new WhitespaceWildcardsFilter("cn", cn));</programlisting>
</example>
<para>
<note>
In addition to simplifying building of complex search filters,
the <literal>Filter</literal> classes also provide proper escaping
of any unsafe characters. This prevents &quot;ldap injection&quot;,
where a user might use such characters to inject unwanted operations
into your LDAP operations.
In addition to simplifying building of complex search parameters,
the <literal>LdapQueryBuilder</literal> and its associated classes
also provide proper escaping of any unsafe characters in search filters.
This prevents &quot;ldap injection&quot;, where a user might use such
characters to inject unwanted operations into your LDAP operations.
</note>
<note>
There are many overloaded methods in <literal>LdapTemplate</literal> for
performing LDAP searches. This is in order to accommodate for as many different use cases
and programming style preferences as possible. For the vast majority of use cases the ones that
take an <literal>LdapQuery</literal> as input will be the recommended methods to use.
</note>
<note>
The <literal>AttributesMapper</literal> is just one of the available callback interfaces to use
when handling search and lookup data. See <xref linkend="dirobjectfactory" /> for alternatives.
</note>
</para>
<para>
For more information on the <literal>LdapQueryBuilder</literal> see <xref linkend="query-builder-advanced" />.
</para>
</sect1>
<sect1>
<sect1 id="ldap-names">
<title>Dynamically Building Distinguished Names</title>
<para>
The standard Java implementation of Distinguished Name, <ulink
@@ -242,9 +252,9 @@ public class PersonDaoImpl implements PersonDao {
...
protected Person buildPerson(Name dn, Attributes attrs) {
Person person = new Person();
person.setCountry(<emphasis>LdapUtils.getStringValue(dn, "c")</emphasis>);
person.setCompany(<emphasis>LdapUtils.getStringValue(dn, "ou")</emphasis>);
person.setFullname(<emphasis>LdapUtils.getStringValue(dn, "cn")</emphasis>);
person.setCountry(<emphasis role="bold">LdapUtils.getStringValue(dn, "c")</emphasis>);
person.setCompany(<emphasis role="bold">LdapUtils.getStringValue(dn, "ou")</emphasis>);
person.setFullname(<emphasis role="bold">LdapUtils.getStringValue(dn, "cn")</emphasis>);
// Populate rest of person object using attributes.
return person;
@@ -253,7 +263,7 @@ protected Person buildPerson(Name dn, Attributes attrs) {
</example>
Since Java version &lt;=1.4 didn't provide any public Distinguished Name implementation at all, Spring LDAP
1.3.2 and lower provided its own implementation, <literal>DistinguishedName</literal>. This implementation
suffered from a couple of shortcomings of its own, and have been deprecated in version 2.0.
suffered from a couple of shortcomings of its own, and has been deprecated in version 2.0.
Users are now recommended to use <literal>LdapName</literal> along with the utilities described above instead.
</para>
</sect1>

View File

@@ -9,7 +9,7 @@
<authorgroup>
<author>
<firstname>Mattias</firstname>
<surname>Arthursson</surname>
<surname>Hellborg Arthursson</surname>
</author>
<author>
<firstname>Ulrik</firstname>
@@ -41,6 +41,7 @@
<xi:include href="overview.xml" />
<xi:include href="basic.xml" />
<xi:include href="dirobjectfactory.xml" />
<xi:include href="advancedqueries.xml" />
<xi:include href="executors.xml" />
<xi:include href="contextprocessor.xml" />
<xi:include href="transactions.xml" />

View File

@@ -15,7 +15,7 @@
<literal>NamingEnumeration</literal>. It also provides a more
comprehensive unchecked Exception hierarchy, built on Spring's
<literal>DataAccessException</literal>. As a bonus, it also contains
classes for dynamically building LDAP filters and DNs (Distinguished
classes for dynamically building LDAP queries and DNs (Distinguished
Names), LDAP attribute management, and client-side LDAP transaction management.</para>
<para>Consider, for example, a method that should search some storage for
@@ -96,6 +96,7 @@ public class TraditionalPersonDaoImpl implements PersonDao {
<informalexample>
<programlisting>package com.example.dao;
import static org.springframework.ldap.query.LdapQueryBuilder.query;
public class PersonDaoImpl implements PersonDao {
private LdapTemplate ldapTemplate;
@@ -106,7 +107,7 @@ public class PersonDaoImpl implements PersonDao {
public List getAllPersonNames() {
return ldapTemplate.<emphasis role="bold">search(
"", "(objectclass=person)"</emphasis>,
query().where("objectclass").is("person")</emphasis>,
new AttributesMapper() {
public Object mapFromAttributes(Attributes attrs)
throws NamingException {
@@ -202,10 +203,6 @@ public class PersonDaoImpl implements PersonDao {
<listitem>
<para><emphasis>spring-jdbc</emphasis> (If you are planning to use the client side compensating transaction support)</para>
</listitem>
<listitem>
<para><emphasis>ldapbp</emphasis> (Sun LDAP Booster Pack - if you will use the LDAP v3 Server controls integration
and you're not using Java5 or higher)</para>
</listitem>
<listitem>
<para><emphasis>commons-pool</emphasis> (If you are planning to use the pooling functionality)</para>
</listitem>
@@ -216,330 +213,63 @@ public class PersonDaoImpl implements PersonDao {
</sect1>
<sect1 id="introduction-package-structure">
<title>Package structure</title>
<para>This section provides an overview of the logical package structure
of the Spring LDAP codebase. The dependencies for each package are clearly
noted.</para>
<figure>
<title>Spring LDAP package structure</title>
<mediaobject>
<imageobject role="fo">
<imagedata fileref="src/docbkx/resources/images/package-dependencies.png"
format="PNG" align="center" />
</imageobject>
<imageobject role="html">
<imagedata fileref="images/package-dependencies.png"
format="PNG" align="center" />
</imageobject>
</mediaobject>
</figure>
<sect2 id="transaction.compensating">
<title>org.springframework.transaction.compensating</title>
<para>The <emphasis>transaction.compensating</emphasis> package contains
the generic compensating transaction support. This is not LDAP-specific
or JNDI-specific in any way.</para>
<itemizedlist spacing="compact">
<listitem>
<para>Dependencies: commons-logging</para>
</listitem>
<sect1 id="new-in-20">
<title>What's new in Spring LDAP 2.0?</title>
<para>
While quite significant modernizations have been made to the Spring LDAP APi in version 2.0, great care has been
taken to ensure backward compatibility as far as possible.
Code that works with Spring LDAP 1.3.x should with very few exceptions still compile and run using the 2.0 libraries
without any modifications whatsoever.
</para>
<para>
The exception is a small number of classes that have been moved to new packages in order to make
a couple of important refactorings possible. The moved classes are usually not part of the intended
public API, and the migration procedure should be very smooth - wherever a Spring LDAP class cannot be found
after upgrade, just organize the imports in your IDE.
</para>
<para>
You will probably encounter some deprecation warnings though, and there are also a lot of other API improvements.
The recommendation for getting as much as possible out of the 2.0 version is to move away from the deprecated
classes and methods and migrate to the new, improved API utilities.
</para>
<para>
Below is a list of the most important changes in Spring LDAP 2.0.
</para>
<itemizedlist>
<listitem>Java 1.6 is now required when using Spring LDAP. Spring versions starting at 2.0 and up are still supported.</listitem>
<listitem>
The central API has been updated with Java 5 features such as generics and varargs. As a consequence,
the entire <literal>spring-ldap-tiger</literal> module has been deprecated and users are encouraged to migrate
to use the core Spring LDAP classes. The parameterization of the core interfaces will most likely cause
lots of compilation warnings, and you are obviously encouraged to take appropriate action to get rid
of these warning.
</listitem>
<listitem>
<literal>DistinguishedName</literal> and associated classes have been deprecated in favor of standard
Java <literal>LdapName</literal>. See <xref linkend="ldap-names" /> for information on how the library
helps working with <literal>LdapNames</literal>.
</listitem>
<listitem>
Fluent LDAP query support has been added. This makes for a more pleasant programming experience when
working with LDAP searches in Spring LDAP. See <xref linkend="basic-queries" /> and
<xref linkend="query-builder-advanced" /> for more information about the LDAP query builder support.
</listitem>
<listitem>
The old <literal>authenticate</literal> methods in <literal>LdapTemplate</literal> have been deprecated
in favor of a couple of new <literal>authenticate</literal> methods that work with
<literal>LdapQuery</literal> objects and <emphasis>throw exceptions</emphasis> on authentication failure,
making it easier for the user to find out what caused an authentication attempt to fail.
</listitem>
</itemizedlist>
</sect2>
<sect2 id="ldap">
<title>org.springframework.ldap</title>
<para>The <emphasis>ldap</emphasis> package contains the exceptions of
the library. These exceptions form an unchecked hierarchy that mirrors
the NamingException hierarchy.</para>
<itemizedlist spacing="compact">
<listitem>
<para>Dependencies: spring-core</para>
</listitem>
</itemizedlist>
</sect2>
<sect2 id="ldap.core">
<title>org.springframework.ldap.core</title>
<para>The <emphasis>ldap.core</emphasis> package contains the central
abstractions of the library. These abstractions include
AuthenticationSource, ContextSource, DirContextProcessor, and
NameClassPairCallbackHandler. This package also contains the central
class LdapTemplate, plus various mappers and executors.</para>
<itemizedlist spacing="compact">
<listitem>
<para>Dependencies: ldap, ldap.support, spring-beans,
spring-core, spring-tx, commons-lang, commons-logging</para>
</listitem>
</itemizedlist>
</sect2>
<sect2 id="ldap.core.support">
<title>org.springframework.ldap.core.support</title>
<para>The <emphasis>ldap.core.support</emphasis> package contains
supporting implementations of some of the core interfaces.</para>
<itemizedlist spacing="compact">
<listitem>
<para>Dependencies: ldap, ldap.core, ldap.support, spring-core,
spring-beans, spring-context, commons-lang, commons-logging</para>
</listitem>
</itemizedlist>
</sect2>
<sect2 id="ldap.core.simple">
<title>org.springframework.ldap.core.simple</title>
<para>The <emphasis>ldap.core.simple</emphasis> package contains
Java5-specific parts of Spring LDAP. It's mainly a simplification
layer that takes advantage of the generics support in Java5, in
order to get typesafe context mappers as well as typesafe search
and lookup methods.</para>
<itemizedlist spacing="compact">
<listitem>
<para>Dependencies: ldap.core</para>
</listitem>
</itemizedlist>
</sect2>
<sect2 id="ldap.pool">
<title>org.springframework.ldap.pool</title>
<para>The <emphasis>ldap.pool</emphasis> package contains
support for detailed pool configuration on a per-ContextSource
basis. Pooling support is provided by PoolingContextSource which
can wrap any ContextSource and pool both read-only and read-write
DirContext objects. Jakarta Commons-Pool is used to provide the
underlying pool implementation.</para>
<itemizedlist spacing="compact">
<listitem>
<para>Dependencies: ldap.core, commons-lang, commons-pool</para>
</listitem>
</itemizedlist>
</sect2>
<sect2 id="ldap.pool.factory">
<title>org.springframework.ldap.pool.factory</title>
<para>The <emphasis>ldap.pool.factory</emphasis> package contains
the actual pooling context source and other classes for context creation.</para>
<itemizedlist spacing="compact">
<listitem>
<para>Dependencies: ldap, ldap.core, ldap.pool, ldap.pool.validation,
spring-beans, spring-tx, commons-lang, commons-logging, commons-pool</para>
</listitem>
</itemizedlist>
</sect2>
<sect2 id="ldap.pool.validation">
<title>org.springframework.ldap.pool.validation</title>
<para>The <emphasis>ldap.pool.validation</emphasis> package contains
the connection validation support.</para>
<itemizedlist spacing="compact">
<listitem>
<para>Dependencies: ldap.pool, commons-lang, commons-logging</para>
</listitem>
</itemizedlist>
</sect2>
<sect2 id="ldap.support">
<title>org.springframework.ldap.support</title>
<para>The <emphasis>ldap.support</emphasis> package contains supporting
utilities, like the exception translation mechanism.</para>
<itemizedlist spacing="compact">
<listitem>
<para>Dependencies: ldap, spring-core, commons-lang, commons-logging</para>
</listitem>
</itemizedlist>
</sect2>
<sect2 id="ldap.authentication">
<title>org.springframework.ldap.authentication</title>
<para>The <emphasis>ldap.authentication</emphasis> package contains an
implementation of the AuthenticationSource interface that can be used
if the user should be allowed to read some information even though not
logged in.</para>
<itemizedlist spacing="compact">
<listitem>
<para>Dependencies: ldap.core, spring-beans, commons-lang</para>
</listitem>
</itemizedlist>
</sect2>
<sect2 id="ldap.control">
<title>org.springframework.ldap.control</title>
<para>The <emphasis>ldap.control</emphasis> package contains an abstract
implementation of the DirContextProcessor interface that can be used as
a basis for processing RequestControls and ResponseControls. There is
also a concrete implementation that handles paged search results and one
that handles sorting. The
<ulink url="http://java.sun.com/products/jndi/">LDAP Booster
Pack</ulink> is used to get support for controls, unless Java5 is used.</para>
<itemizedlist spacing="compact">
<listitem>
<para>Dependencies: ldap, ldap.core, LDAP booster pack (optional), spring-core,
commons-lang, commons-logging</para>
</listitem>
</itemizedlist>
</sect2>
<sect2 id="ldap.filter">
<title>org.springframework.ldap.filter</title>
<para>The <emphasis>ldap.filter</emphasis> package contains the Filter
abstraction and several implementations of it.</para>
<itemizedlist spacing="compact">
<listitem>
<para>Dependencies: ldap.core, spring-core, commons-lang</para>
</listitem>
</itemizedlist>
</sect2>
<sect2 id="ldap.transaction.compensating">
<title>org.springframework.ldap.transaction.compensating</title>
<para>The <emphasis>ldap.transaction.compensating</emphasis> package contains the
core LDAP-specific implementation of compensating transactions.</para>
<itemizedlist spacing="compact">
<listitem>
<para>Dependencies: ldap.core, ldap.core.support, transaction.compensating,
spring-core, commons-lang, commons-logging</para>
</listitem>
</itemizedlist>
</sect2>
<sect2 id="ldap.transaction.compensating.manager">
<title>org.springframework.ldap.transaction.compensating.manager</title>
<para>The <emphasis>ldap.transaction.compensating.manager</emphasis> package contains the
core implementation classes for client-side compensating transactions.</para>
<itemizedlist spacing="compact">
<listitem>
<para>Dependencies: ldap, ldap.core, ldap.support, ldap.transaction.compensating,
ldap.transaction.compensating.support, transaction.compensating,
spring-tx, spring-jdbc, spring-orm, commons-logging</para>
</listitem>
</itemizedlist>
</sect2>
<sect2 id="ldap.transaction.compensating.support">
<title>org.springframework.ldap.transaction.compensating.support</title>
<para>The <emphasis>ldap.transaction.compensating.support</emphasis> package contains
useful helper classes for client-side compensating transactions.</para>
<itemizedlist spacing="compact">
<listitem>
<para>Dependencies: ldap.core, ldap.transaction.compensating</para>
</listitem>
</itemizedlist>
</sect2>
<sect2 id="ldap.ldif">
<title>org.springframework.ldap.ldif</title>
<para>The ldap.ldif package provides support for parsing LDIF
files.</para>
<itemizedlist>
<listitem>
<para>Dependencies: ldap.core</para>
</listitem>
</itemizedlist>
</sect2>
<sect2 id="ldap.ldif.batch">
<title>org.springframework.ldap.ldif.batch</title>
<para>The ldap.ldif.batch package provides the classes necessary to
use the LDIF parser in the Spring Batch framework.</para>
<itemizedlist>
<listitem>
<para>Dependencies: ldap.core, ldap.ldif.parser, spring-batch,
spring-core, spring-beans, commons-logging</para>
</listitem>
</itemizedlist>
</sect2>
<sect2 id="ldap.ldif.parser">
<title>org.springframework.ldap.ldif.parser</title>
<para>The ldap.ldif.parser package provides the parser classes
and interfaces.</para>
<itemizedlist>
<listitem>
<para>Dependencies: ldap.core, ldap.schema, ldap.ldif, ldap.ldif.support,
spring-core, spring-beans, commons-lang, commons-logging</para>
</listitem>
</itemizedlist>
</sect2>
<sect2 id="ldap.ldif.support">
<title>org.springframework.ldap.ldif.support</title>
<para>The ldap.ldif.support package provides the necessary auxiliary
classes utilized by the LDIF Parser.</para>
<itemizedlist>
<listitem>
<para>Dependencies: ldap.core, ldap.ldif, commons-lang, commons-logging</para>
</listitem>
</itemizedlist>
</sect2>
<sect2 id="ldap.odm">
<title>org.springframework.ldap.odm</title>
<para>The ldap.odm package provides the classes and interfaces
enabling
annotation based object-directory mapping.</para>
<itemizedlist>
<listitem>
<para>Dependencies: ldap, ldap.core, ldap.core.simple, ldap.filter, spring-beans,
commons-cli, commons-logging, freemarker</para>
</listitem>
</itemizedlist>
</sect2>
<para>For the exact list of jar dependencies, see the Spring LDAP Maven2
Project Object Model (POM) files in the source tree.</para>
</sect1>
<sect1 id="introduction-support">
<title>Support</title>
<para>Spring LDAP 1.3 is supported on Spring 2.0 and later.</para>
<para>Spring LDAP 2.0 is supported on Spring 2.0 and later.</para>
<para>The community support forum is located at <ulink
url="http://forum.springframework.org">http://forum.springframework.org</ulink>,
url="http://forum.spring.io/forum/spring-projects/data/ldap">http://forum.spring.io/forum/spring-projects/data/ldap</ulink>,
and the project web page is <ulink
url="http://www.springframework.org/ldap">http://www.springframework.org/ldap</ulink>.</para>
url="http://projects.spring.io/spring-ldap/">http://projects.spring.io/spring-ldap/</ulink>.</para>
</sect1>
</chapter>

View File

@@ -80,7 +80,7 @@
&lt;property name="ldapTemplate" ref="ldapTemplate" /&gt;
&lt;/bean&gt;
&lt;tx:annotation-driven&gt;
&lt;tx:annotation-driven /&gt;
...</programlisting>
<note>While the this setup will work fine for most simple use cases, some more complex scenarios will

View File

@@ -35,8 +35,7 @@
LDAP search based on e.g. the user name to get this DN:</para>
<para><programlisting>private String getDnForUser(String uid) {
Filter f = new EqualsFilter("uid", uid);
List result = ldapTemplate.search(LdapUtils.emptyLdapName(), f.toString(),
List result = ldapTemplate.search(query().where("uid").is(uid),
new AbstractContextMapper() {
protected Object doMapFromContext(DirContextOperations ctx) {
return ctx.getNameInNamespace();
@@ -52,29 +51,15 @@
forced to concern herself with the DN of the user, she can only search for
the user's uid, and the search always starts at the root of the tree (the
empty path). A more flexible method would let the user specify the search
base, the search filter, and the credentials. Spring LDAP 1.3.0 introduced
new authenticate methods in LdapTemplate that provide this
functionality:</para>
<itemizedlist>
<listitem>
<para><literal>boolean authenticate(Name base, String filter, String
password);</literal></para>
</listitem>
<listitem>
<para><literal>boolean authenticate(String base, String filter, String
password);</literal></para>
</listitem>
</itemizedlist>
<para>Using one of these methods, authentication becomes as simple as
this:</para>
base, the search filter, and the credentials. Spring LDAP includes an authenticate
method in LdapTemplate that provide this functionality: <literal>boolean authenticate(LdapQuery query, String password);</literal>
</para>
<para>Using this method authentication becomes as simple as this:</para>
<para><example>
<title>Authenticating a user using Spring LDAP.</title>
<programlisting>boolean authenticated = ldapTemplate.authenticate("", "(uid=john.doe)", "secret");</programlisting>
<programlisting>ldapTemplate.authenticate(query().where("uid").is("john.doe"), "secret");</programlisting>
</example>
<note>
As described in below, some setups may require additional operations to be performed
@@ -116,25 +101,18 @@
// It is imperative that the created DirContext instance is always closed
LdapUtils.closeContext(ctx);
}
}</programlisting>It would be better if the operation could be provided as an
}</programlisting>
It would be better if the operation could be provided as an
implementation of a callback interface, thus not limiting the operation to
always be a <literal>lookup</literal>. Spring LDAP 1.3.0 introduced the
callback interface
<literal>AuthenticatedLdapEntryContextCallback</literal> and a few
corresponding <literal>authenticate</literal> methods:</para>
always be a <literal>lookup</literal>. Spring LDAP includes the callback interface
<literal>AuthenticatedLdapEntryContextMapper</literal> and a
corresponding <literal>authenticate</literal> method:
<literal>&lt;T&gt; T authenticate(LdapQuery query, String password, AuthenticatedLdapEntryContextMapper&lt;T&gt; mapper);</literal></para>
<itemizedlist>
<listitem>
<para><literal>boolean authenticate(Name base, String filter, String
password, AuthenticatedLdapEntryContextCallback
callback);</literal></para>
</listitem>
<listitem>
<listitem>
<para><literal>boolean authenticate(String base, String filter, String
password, AuthenticatedLdapEntryContextCallback
callback);</literal></para>
</listitem>
</listitem>
</itemizedlist>
<para>This opens up for any operation to be performed on the authenticated
@@ -144,83 +122,30 @@
<title>Performing an LDAP operation on the authenticated context using
Spring LDAP.</title>
<programlisting>AuthenticatedLdapEntryContextCallback contextCallback = new AuthenticatedLdapEntryContextCallback() {
public void executeWithContext(DirContext ctx, LdapEntryIdentification ldapEntryIdentification) {
<programlisting>AuthenticatedLdapEntryContextMapper&lt;DirContextOperations&gt; mapper = new AuthenticatedLdapEntryContextMapper&lt;DirContextOperations&gt;() {
public DirContextOperations mapWithContext(DirContext ctx, LdapEntryIdentification ldapEntryIdentification) {
try {
ctx.lookup(ldapEntryIdentification.getRelativeDn());
return (DirContextOperations) ctx.lookup(ldapEntryIdentification.getRelativeName());
}
catch (NamingException e) {
throw new RuntimeException("Failed to lookup " + ldapEntryIdentification.getRelativeDn(), e);
throw new RuntimeException("Failed to lookup " + ldapEntryIdentification.getRelativeName(), e);
}
}
};
ldapTemplate.authenticate("", "(uid=john.doe)", "secret", contextCallback));</programlisting>
ldapTemplate.authenticate(query().where("uid").is("john.doe"), "secret", mapper);</programlisting>
</example>
</sect1>
<sect1>
<title>Retrieving the Authentication Exception</title>
<title>Obsolete authentication methods</title>
<para>So far, the methods have only been able to tell the user whether or
not the authentication succeeded. There has been no way of retrieving the
actual exception. Spring LDAP 1.3.1 introduced the
<literal>AuthenticationErrorCallback</literal> and a few more
<literal>authenticate</literal> methods:</para>
<itemizedlist>
<listitem>
<para><literal>boolean authenticate(Name base, String filter, String
password, AuthenticationErrorCallback errorCallback);</literal></para>
</listitem>
<listitem>
<para><literal>boolean authenticate(String base, String filter, String
password, AuthenticationErrorCallback errorCallback);</literal></para>
</listitem>
<listitem>
<para><literal>boolean authenticate(Name base, String filter, String
password, AuthenticatedLdapEntryContextCallback callback,
AuthenticationErrorCallback errorCallback);</literal></para>
</listitem>
<listitem>
<para><literal>boolean authenticate(String base, String filter, String
password, AuthenticatedLdapEntryContextCallback callback,
AuthenticationErrorCallback errorCallback);</literal></para>
</listitem>
</itemizedlist>
<para>A convenient collecting implementation of the error callback
interface is also provided:</para>
<para><programlisting>public final class CollectingAuthenticationErrorCallback implements AuthenticationErrorCallback {
private Exception error;
public void execute(Exception e) {
this.error = e;
}
public Exception getError() {
return error;
}
}</programlisting>The code needed for authenticating a user and retrieving the
authentication exception in case of an error boils down to this:</para>
<para><example>
<title>Authenticating a user and retrieving the authentication
exception.</title>
<programlisting>import org.springframework.ldap.core.support.CollectingAuthenticationErrorCallback;
...
CollectingAuthenticationErrorCallback errorCallback = new CollectingAuthenticationErrorCallback();
boolean result = ldapTemplate.authenticate("", filter.toString(), "invalidpassword", errorCallback);
if (!result) {
Exception error = errorCallback.getError();
// error is likely of type org.springframework.ldap.AuthenticationException
}</programlisting>
</example></para>
<para>
In addition to the <literal>authenticate</literal> methods described above
there are a number of deprecated methods that can be used for authentication.
While these will work fine, the recommendation is to use the
<literal>LdapQuery</literal> methods instead.
</para>
</sect1>
<sect1>

View File

@@ -16,29 +16,31 @@
package org.springframework.ldap.itest;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertNotNull;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import junit.framework.Assert;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.ldap.AuthenticationException;
import org.springframework.ldap.core.AuthenticatedLdapEntryContextCallback;
import org.springframework.ldap.core.CollectingAuthenticationErrorCallback;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.LdapEntryIdentification;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.core.support.CollectingAuthenticationErrorCallback;
import org.springframework.ldap.core.support.LookupAttemptingCallback;
import org.springframework.ldap.filter.AndFilter;
import org.springframework.ldap.filter.EqualsFilter;
import org.springframework.ldap.filter.WhitespaceWildcardsFilter;
import org.springframework.test.context.ContextConfiguration;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertNotNull;
import static org.springframework.ldap.query.LdapQueryBuilder.query;
/**
* Tests the authenticate methods of LdapTemplate.
*
@@ -58,14 +60,34 @@ public class LdapTemplateAuthenticationITest extends AbstractLdapTemplateIntegra
assertTrue(tested.authenticate("", filter.toString(), "password"));
}
@Test
@Test
public void testAuthenticateWithLdapQuery() {
AndFilter filter = new AndFilter();
filter.and(new EqualsFilter("objectclass", "person")).and(new EqualsFilter("uid", "some.person3"));
tested.authenticate(query()
.where("objectclass").is("person")
.and("uid").is("some.person3"),
"password");
}
@Test
public void testAuthenticateWithInvalidPassword() {
AndFilter filter = new AndFilter();
filter.and(new EqualsFilter("objectclass", "person")).and(new EqualsFilter("uid", "some.person3"));
assertFalse(tested.authenticate("", filter.toString(), "invalidpassword"));
}
@Test
@Test(expected = AuthenticationException.class)
public void testAuthenticateWithLdapQueryAndInvalidPassword() {
AndFilter filter = new AndFilter();
filter.and(new EqualsFilter("objectclass", "person")).and(new EqualsFilter("uid", "some.person3"));
tested.authenticate(query()
.where("objectclass").is("person")
.and("uid").is("some.person3"),
"invalidpassword");
}
@Test
public void testAuthenticateWithLookupOperationPerformedOnAuthenticatedContext() {
AndFilter filter = new AndFilter();
filter.and(new EqualsFilter("objectclass", "person")).and(new EqualsFilter("uid", "some.person3"));
@@ -83,7 +105,28 @@ public class LdapTemplateAuthenticationITest extends AbstractLdapTemplateIntegra
assertTrue(tested.authenticate("", filter.toString(), "password", contextCallback));
}
@Test
@Test
public void testAuthenticateWithLdapQueryAndMapper() {
DirContextOperations ctx = tested.authenticate(query()
.where("objectclass").is("person")
.and("uid").is("some.person3"),
"password",
new LookupAttemptingCallback());
Assert.assertNotNull(ctx);
assertEquals("some.person3", ctx.getStringAttribute("uid"));
}
@Test(expected = AuthenticationException.class)
public void testAuthenticateWithLdapQueryAndMapperAndInvalidPassword() {
DirContextOperations ctx = tested.authenticate(query()
.where("objectclass").is("person")
.and("uid").is("some.person3"),
"invalidpassword",
new LookupAttemptingCallback());
}
@Test
public void testAuthenticateWithInvalidPasswordAndCollectedException() {
AndFilter filter = new AndFilter();
filter.and(new EqualsFilter("objectclass", "person")).and(new EqualsFilter("uid", "some.person3"));