SEC-607: Deprecated InitialDirContextFactory and replaced it with SpringSecurityContextSource.

Also some refactoring of LdapUserDetailsManager to use a strategy for creating DNs from usernames.
This commit is contained in:
Luke Taylor 2007-11-20 20:54:48 +00:00
parent 79adaa2d3d
commit 9e2f372bad
31 changed files with 527 additions and 389 deletions

View File

@ -56,7 +56,7 @@
<dependency>
<groupId>org.springframework.ldap</groupId>
<artifactId>spring-ldap</artifactId>
<version>1.2</version>
<version>1.2.1-SNAPSHOT</version>
<optional>true</optional>
</dependency>
<dependency>

View File

@ -1,22 +1,23 @@
package org.springframework.security.config;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
import org.springframework.security.providers.ldap.LdapAuthenticationProvider;
import org.springframework.security.providers.ldap.authenticator.BindAuthenticator;
import org.springframework.security.providers.ldap.populator.DefaultLdapAuthoritiesPopulator;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.util.StringUtils;
import org.springframework.util.Assert;
import org.springframework.security.ldap.DefaultInitialDirContextFactory;
import org.springframework.security.providers.ldap.LdapAuthenticationProvider;
import org.springframework.security.providers.ldap.populator.DefaultLdapAuthoritiesPopulator;
import org.springframework.security.providers.ldap.authenticator.BindAuthenticator;
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.ldap.core.DirContextAdapter;
import org.w3c.dom.Element;
import org.apache.directory.server.configuration.MutableServerStartupConfiguration;
import org.apache.directory.server.core.partition.impl.btree.MutableBTreePartitionConfiguration;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.directory.server.configuration.MutableServerStartupConfiguration;
import org.apache.directory.server.core.partition.impl.btree.MutableBTreePartitionConfiguration;
import org.w3c.dom.Element;
import javax.naming.NamingException;
import java.util.HashSet;
@ -63,13 +64,13 @@ public class LdapBeanDefinitionParser extends AbstractBeanDefinitionParser {
protected AbstractBeanDefinition parseInternal(Element elt, ParserContext parserContext) {
String url = elt.getAttribute(URL_ATTRIBUTE);
RootBeanDefinition initialDirContextFactory;
RootBeanDefinition contextSource;
if (!StringUtils.hasText(url)) {
initialDirContextFactory = createEmbeddedServer(elt, parserContext);
contextSource = createEmbeddedServer(elt, parserContext);
} else {
initialDirContextFactory = new RootBeanDefinition(DefaultInitialDirContextFactory.class);
initialDirContextFactory.getConstructorArgumentValues().addIndexedArgumentValue(0, url);
contextSource = new RootBeanDefinition(DefaultSpringSecurityContextSource.class);
contextSource.getConstructorArgumentValues().addIndexedArgumentValue(0, url);
}
String managerDn = elt.getAttribute(PRINCIPAL_ATTRIBUTE);
@ -79,14 +80,14 @@ public class LdapBeanDefinitionParser extends AbstractBeanDefinitionParser {
Assert.hasText(managerPassword, "You must specify the " + PASSWORD_ATTRIBUTE +
" if you supply a " + managerDn);
initialDirContextFactory.getPropertyValues().addPropertyValue("managerDn", managerDn);
initialDirContextFactory.getPropertyValues().addPropertyValue("managerPassword", managerPassword);
contextSource.getPropertyValues().addPropertyValue("userDn", managerDn);
contextSource.getPropertyValues().addPropertyValue("password", managerPassword);
}
// TODO: Make these default values for 2.0
initialDirContextFactory.getPropertyValues().addPropertyValue("useLdapContext", Boolean.TRUE);
initialDirContextFactory.getPropertyValues().addPropertyValue("dirObjectFactory", "org.springframework.ldap.core.support.DefaultDirObjectFactory");
// contextSource.getPropertyValues().addPropertyValue("useLdapContext", Boolean.TRUE);
// contextSource.getPropertyValues().addPropertyValue("dirObjectFactory", "org.springframework.ldap.core.support.DefaultDirObjectFactory");
String id = elt.getAttribute(ID_ATTRIBUTE);
String contextSourceId = "contextSource";
@ -99,13 +100,13 @@ public class LdapBeanDefinitionParser extends AbstractBeanDefinitionParser {
logger.warn("Bean already exists with Id '" + contextSourceId + "'");
}
parserContext.getRegistry().registerBeanDefinition(contextSourceId, initialDirContextFactory);
parserContext.getRegistry().registerBeanDefinition(contextSourceId, contextSource);
RootBeanDefinition bindAuthenticator = new RootBeanDefinition(BindAuthenticator.class);
bindAuthenticator.getConstructorArgumentValues().addGenericArgumentValue(initialDirContextFactory);
bindAuthenticator.getConstructorArgumentValues().addGenericArgumentValue(contextSource);
bindAuthenticator.getPropertyValues().addPropertyValue("userDnPatterns", new String[] {DEFAULT_DN_PATTERN});
RootBeanDefinition authoritiesPopulator = new RootBeanDefinition(DefaultLdapAuthoritiesPopulator.class);
authoritiesPopulator.getConstructorArgumentValues().addGenericArgumentValue(initialDirContextFactory);
authoritiesPopulator.getConstructorArgumentValues().addGenericArgumentValue(contextSource);
authoritiesPopulator.getConstructorArgumentValues().addGenericArgumentValue(DEFAULT_GROUP_CONTEXT);
RootBeanDefinition ldapProvider = new RootBeanDefinition(LdapAuthenticationProvider.class);
@ -170,16 +171,15 @@ public class LdapBeanDefinitionParser extends AbstractBeanDefinitionParser {
configuration.setExitVmOnShutdown(false);
configuration.setContextPartitionConfigurations(partitions);
RootBeanDefinition initialDirContextFactory = new RootBeanDefinition(DefaultInitialDirContextFactory.class);
initialDirContextFactory.getConstructorArgumentValues().addIndexedArgumentValue(0,
"ldap://127.0.0.1:" + port + "/" + suffix);
RootBeanDefinition contextSource = new RootBeanDefinition(DefaultSpringSecurityContextSource.class);
contextSource.getConstructorArgumentValues().addIndexedArgumentValue(0, "ldap://127.0.0.1:" + port + "/" + suffix);
initialDirContextFactory.getPropertyValues().addPropertyValue("managerDn", "uid=admin,ou=system");
initialDirContextFactory.getPropertyValues().addPropertyValue("managerPassword", "secret");
contextSource.getPropertyValues().addPropertyValue("userDn", "uid=admin,ou=system");
contextSource.getPropertyValues().addPropertyValue("password", "secret");
RootBeanDefinition apacheDSStartStop = new RootBeanDefinition(ApacheDSContainer.class);
apacheDSStartStop.getConstructorArgumentValues().addGenericArgumentValue(configuration);
apacheDSStartStop.getConstructorArgumentValues().addGenericArgumentValue(initialDirContextFactory);
apacheDSStartStop.getConstructorArgumentValues().addGenericArgumentValue(contextSource);
if (parserContext.getRegistry().containsBeanDefinition("_apacheDSStartStopBean")) {
parserContext.getReaderContext().error("Only one embedded server bean is allowed per application context",
@ -188,7 +188,7 @@ public class LdapBeanDefinitionParser extends AbstractBeanDefinitionParser {
parserContext.getRegistry().registerBeanDefinition("_apacheDSStartStopBean", apacheDSStartStop);
return initialDirContextFactory;
return contextSource;
}

View File

@ -28,6 +28,7 @@ import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.util.Assert;
import org.springframework.ldap.UncategorizedLdapException;
import org.springframework.ldap.core.support.DefaultDirObjectFactory;
import org.springframework.ldap.core.DistinguishedName;
import org.springframework.dao.DataAccessException;
import java.util.Hashtable;
@ -64,10 +65,14 @@ import javax.naming.directory.InitialDirContext;
* @author Luke Taylor
* @version $Id$
*
*
* @deprecated use {@link DefaultSpringSecurityContextSource} instead.
*
* @see <a href="http://java.sun.com/products/jndi/tutorial/ldap/connect/pool.html">The Java tutorial's guide to LDAP
* connection pooling</a>
*/
public class DefaultInitialDirContextFactory implements InitialDirContextFactory, MessageSourceAware {
public class DefaultInitialDirContextFactory implements InitialDirContextFactory,
SpringSecurityContextSource, MessageSourceAware {
//~ Static fields/initializers =====================================================================================
private static final Log logger = LogFactory.getLog(DefaultInitialDirContextFactory.class);
@ -344,4 +349,16 @@ public class DefaultInitialDirContextFactory implements InitialDirContextFactory
public void setDirObjectFactory(String dirObjectFactory) {
this.dirObjectFactoryClass = dirObjectFactory;
}
public DirContext getReadWriteContext(String userDn, Object credentials) {
return newInitialDirContext(userDn, (String) credentials);
}
public DistinguishedName getBaseLdapPath() {
return new DistinguishedName(rootDn);
}
public String getBaseLdapPathAsString() {
return getBaseLdapPath().toString();
}
}

View File

@ -0,0 +1,34 @@
package org.springframework.security.ldap;
import org.springframework.ldap.core.DistinguishedName;
/**
* @author Luke Taylor
* @version $Id$
*/
public class DefaultLdapUsernameToDnMapper implements LdapUsernameToDnMapper {
private String userDnBase;
private String usernameAttribute;
/**
* This implementation appends a name component to the <tt>userDnBase</tt> context using the
* <tt>usernameAttributeName</tt> property. So if the <tt>uid</tt> attribute is used to store the username, and the
* base DN is <tt>cn=users</tt> and we are creating a new user called "sam", then the DN will be
* <tt>uid=sam,cn=users</tt>.
*
* @param userDnBase the base name of the DN
* @param usernameAttribute the attribute to append for the username component.
*/
public DefaultLdapUsernameToDnMapper(String userDnBase, String usernameAttribute) {
this.userDnBase = userDnBase;
this.usernameAttribute = usernameAttribute;
}
public DistinguishedName buildDn(String username) {
DistinguishedName dn = new DistinguishedName(userDnBase);
dn.add(usernameAttribute, username);
return dn;
}
}

View File

@ -0,0 +1,116 @@
package org.springframework.security.ldap;
import org.springframework.security.BadCredentialsException;
import org.springframework.security.SpringSecurityMessageSource;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.ldap.core.support.LdapContextSource;
import org.springframework.util.Assert;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.StringTokenizer;
/**
* SpringSecurityContextSource implementation which uses Spring LDAP's <tt>LdapContextSource</tt> as a base
* class. Intended as a replacement for <tt>DefaultInitialDirContextFactory</tt> from versions of the framework prior
* to 2.0.
*
* @author Luke Taylor
* @version $Id$
* @since 2.0
*/
public class DefaultSpringSecurityContextSource extends LdapContextSource implements SpringSecurityContextSource,
MessageSourceAware {
private static final Log logger = LogFactory.getLog(DefaultSpringSecurityContextSource.class);
private String rootDn;
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
/**
* Create and initialize an instance which will connect to the supplied LDAP URL.
*
* @param providerUrl an LDAP URL of the form <code>ldap://localhost:389/base_dn<code>
*/
public DefaultSpringSecurityContextSource(String providerUrl) {
Assert.hasLength(providerUrl, "An LDAP connection URL must be supplied.");
StringTokenizer st = new StringTokenizer(providerUrl);
ArrayList urls = new ArrayList();
// Work out rootDn from the first URL and check that the other URLs (if any) match
while (st.hasMoreTokens()) {
String url = st.nextToken();
String urlRootDn = LdapUtils.parseRootDnFromUrl(url);
urls.add(url.substring(0, url.lastIndexOf(urlRootDn)));
logger.info(" URL '" + url + "', root DN is '" + urlRootDn + "'");
if (rootDn == null) {
rootDn = urlRootDn;
} else if (!rootDn.equals(urlRootDn)) {
throw new IllegalArgumentException("Root DNs must be the same when using multiple URLs");
}
}
super.setUrls((String[]) urls.toArray(new String[urls.size()]));
super.setBase(rootDn);
}
public DirContext getReadWriteContext(String userDn, Object credentials) {
Hashtable env = new Hashtable(getAnonymousEnv());
env.put(Context.SECURITY_PRINCIPAL, userDn);
env.put(Context.SECURITY_CREDENTIALS, credentials);
if (logger.isDebugEnabled()) {
logger.debug("Creating context with principal: '" + userDn + "'");
}
try {
return createContext(env);
} catch (org.springframework.ldap.NamingException e) {
if ((e instanceof org.springframework.ldap.AuthenticationException)
|| (e instanceof org.springframework.ldap.OperationNotSupportedException)) {
throw new BadCredentialsException(
messages.getMessage("DefaultSpringSecurityContextSource.badCredentials", "Bad credentials"), e);
}
throw e;
}
}
/** Copied from parent <tt>AbstractContextSource</tt> as package private */
DirContext createContext(Hashtable environment) {
DirContext ctx = null;
try {
ctx = getDirContextInstance(environment);
if (logger.isInfoEnabled()) {
Hashtable ctxEnv = ctx.getEnvironment();
String ldapUrl = (String) ctxEnv.get(Context.PROVIDER_URL);
logger.debug("Got Ldap context on server '" + ldapUrl + "'");
}
return ctx;
}
catch (NamingException e) {
LdapUtils.closeContext(ctx);
throw org.springframework.ldap.support.LdapUtils.convertLdapException(e);
}
}
public void setMessageSource(MessageSource messageSource) {
this.messages = new MessageSourceAccessor(messageSource);
}
}

View File

@ -28,7 +28,7 @@ import javax.naming.directory.DirContext;
* @author Luke Taylor
* @version $Id$
*/
public interface InitialDirContextFactory extends ContextSource {
public interface InitialDirContextFactory {
//~ Methods ========================================================================================================
/**

View File

@ -0,0 +1,13 @@
package org.springframework.security.ldap;
import org.springframework.ldap.core.DistinguishedName;
/**
* Constructs an Ldap Distinguished Name from a username.
*
* @author Luke Taylor
* @version $Id$
*/
public interface LdapUsernameToDnMapper {
DistinguishedName buildDn(String username);
}

View File

@ -15,17 +15,18 @@
package org.springframework.security.ldap;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.DistinguishedName;
import org.springframework.util.Assert;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.Assert;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.DistinguishedName;
import java.io.UnsupportedEncodingException;
import javax.naming.Context;
import javax.naming.NamingException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
/**
@ -156,20 +157,8 @@ public final class LdapUtils {
String urlRootDn = "";
if (url.startsWith("ldap:") || url.startsWith("ldaps:")) {
// URI uri = parseLdapUrl(url);
// urlRootDn = uri.getPath();
// skip past the "://"
int colon = url.indexOf(':');
url = url.substring(colon + 3);
// Match the slash at the end of the address (if there)
int slash = url.indexOf('/');
if (slash >= 0) {
urlRootDn = url.substring(slash);
}
URI uri = parseLdapUrl(url);
urlRootDn = uri.getPath();
} else {
// Assume it's an embedded server
urlRootDn = url;
@ -182,7 +171,6 @@ public final class LdapUtils {
return urlRootDn;
}
// removed for 1.3 compatibility
/**
* Parses the supplied LDAP URL.
* @param url the URL (e.g. <tt>ldap://monkeymachine:11389/dc=springframework,dc=org</tt>).
@ -190,15 +178,15 @@ public final class LdapUtils {
* @throws IllegalArgumentException if the URL is null, empty or the URI syntax is invalid.
*/
// private static URI parseLdapUrl(String url) {
// Assert.hasLength(url);
//
// try {
// return new URI(url);
// } catch (URISyntaxException e) {
// IllegalArgumentException iae = new IllegalArgumentException("Unable to parse url: " + url);
// iae.initCause(e);
// throw iae;
// }
// }
private static URI parseLdapUrl(String url) {
Assert.hasLength(url);
try {
return new URI(url);
} catch (URISyntaxException e) {
IllegalArgumentException iae = new IllegalArgumentException("Unable to parse url: " + url);
iae.initCause(e);
throw iae;
}
}
}

View File

@ -0,0 +1,26 @@
package org.springframework.security.ldap;
import org.springframework.ldap.core.support.BaseLdapPathContextSource;
import org.springframework.ldap.core.ContextSource;
import javax.naming.directory.DirContext;
/**
* Extension of {@link ContextSource} which allows binding explicitly as a particular user.
*
* @author Luke Taylor
* @version $Id$
* @since 2.0
*/
public interface SpringSecurityContextSource extends BaseLdapPathContextSource {
/**
* Obtains a context using the supplied distinguished name and credentials.
*
* @param userDn the distinguished name of the user to authenticate as
* @param credentials the user's password
* @return a context authenticated as the supplied user
*/
DirContext getReadWriteContext(String userDn, Object credentials);
}

View File

@ -18,7 +18,6 @@ package org.springframework.security.ldap;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.ldap.core.ContextExecutor;
import org.springframework.ldap.core.ContextSource;
import org.springframework.ldap.core.DirContextAdapter;
@ -35,10 +34,8 @@ import java.util.List;
import java.util.ArrayList;
import java.text.MessageFormat;
import javax.naming.NameNotFoundException;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.Context;
import javax.naming.NameClassPair;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
@ -97,9 +94,9 @@ public class SpringSecurityLdapTemplate extends org.springframework.ldap.core.Ld
ctls.setReturningAttributes(NO_ATTRS);
ctls.setSearchScope(SearchControls.OBJECT_SCOPE);
String relativeName = LdapUtils.getRelativeName(dn, ctx);
// String relativeName = LdapUtils.getRelativeName(dn, ctx);
NamingEnumeration results = ctx.search(relativeName, comparisonFilter, new Object[] {value}, ctls);
NamingEnumeration results = ctx.search(dn, comparisonFilter, new Object[] {value}, ctls);
return Boolean.valueOf(results.hasMore());
}
@ -110,25 +107,25 @@ public class SpringSecurityLdapTemplate extends org.springframework.ldap.core.Ld
return matches.booleanValue();
}
public boolean nameExists(final String dn) {
Boolean exists = (Boolean) executeReadOnly(new ContextExecutor() {
public Object executeWithContext(DirContext ctx) throws NamingException {
try {
Object obj = ctx.lookup(LdapUtils.getRelativeName(dn, ctx));
if (obj instanceof Context) {
LdapUtils.closeContext((Context) obj);
}
} catch (NameNotFoundException nnfe) {
return Boolean.FALSE;
}
return Boolean.TRUE;
}
});
return exists.booleanValue();
}
// public boolean nameExists(final String dn) {
// Boolean exists = (Boolean) executeReadOnly(new ContextExecutor() {
// public Object executeWithContext(DirContext ctx) throws NamingException {
// try {
// Object obj = ctx.lookup(dn);
// if (obj instanceof Context) {
// LdapUtils.closeContext((Context) obj);
// }
//
// } catch (NameNotFoundException nnfe) {
// return Boolean.FALSE;
// }
//
// return Boolean.TRUE;
// }
// });
//
// return exists.booleanValue();
// }
/**
* Composes an object from the attributes of the given DN.
@ -142,7 +139,7 @@ public class SpringSecurityLdapTemplate extends org.springframework.ldap.core.Ld
return (DirContextOperations) executeReadOnly(new ContextExecutor() {
public Object executeWithContext(DirContext ctx) throws NamingException {
Attributes attrs = ctx.getAttributes(LdapUtils.getRelativeName(dn, ctx), attributesToRetrieve);
Attributes attrs = ctx.getAttributes(dn, attributesToRetrieve);
// Object object = ctx.lookup(LdapUtils.getRelativeName(dn, ctx));
@ -255,12 +252,12 @@ public class SpringSecurityLdapTemplate extends org.springframework.ldap.core.Ld
dn.append(base);
}
String nameInNamespace = ctx.getNameInNamespace();
if (StringUtils.hasLength(nameInNamespace)) {
dn.append(",");
dn.append(nameInNamespace);
}
// String nameInNamespace = ctx.getNameInNamespace();
//
// if (StringUtils.hasLength(nameInNamespace)) {
// dn.append(",");
// dn.append(nameInNamespace);
// }
return new DirContextAdapter(searchResult.getAttributes(), new DistinguishedName(dn.toString()));
}

View File

@ -15,7 +15,6 @@
package org.springframework.security.ldap.search;
import org.springframework.security.ldap.InitialDirContextFactory;
import org.springframework.security.ldap.SpringSecurityLdapTemplate;
import org.springframework.security.ldap.LdapUserSearch;
@ -30,6 +29,7 @@ import org.springframework.util.Assert;
import org.springframework.ldap.core.ContextSource;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.support.BaseLdapPathContextSource;
import javax.naming.directory.SearchControls;
@ -50,7 +50,7 @@ public class FilterBasedLdapUserSearch implements LdapUserSearch {
//~ Instance fields ================================================================================================
private ContextSource initialDirContextFactory;
private ContextSource contextSource;
/**
* The LDAP SearchControls object used for the search. Shared between searches so shouldn't be modified
@ -58,14 +58,15 @@ public class FilterBasedLdapUserSearch implements LdapUserSearch {
*/
private SearchControls searchControls = new SearchControls();
/** Context name to search in, relative to the root DN of the configured InitialDirContextFactory. */
/** Context name to search in, relative to the base of the configured ContextSource. */
private String searchBase = "";
/**
* The filter expression used in the user search. This is an LDAP search filter (as defined in 'RFC 2254')
* with optional arguments. See the documentation for the <tt>search</tt> methods in {@link
* javax.naming.directory.DirContext DirContext} for more information.<p>In this case, the username is the
* only parameter.</p>
* javax.naming.directory.DirContext DirContext} for more information.
*
* <p>In this case, the username is the only parameter.</p>
* Possible examples are:
* <ul>
* <li>(uid={0}) - this would search for a username match on the uid attribute.</li>
@ -75,19 +76,18 @@ public class FilterBasedLdapUserSearch implements LdapUserSearch {
//~ Constructors ===================================================================================================
public FilterBasedLdapUserSearch(String searchBase, String searchFilter,
InitialDirContextFactory initialDirContextFactory) {
Assert.notNull(initialDirContextFactory, "initialDirContextFactory must not be null");
public FilterBasedLdapUserSearch(String searchBase, String searchFilter, BaseLdapPathContextSource contextSource) {
Assert.notNull(contextSource, "contextSource must not be null");
Assert.notNull(searchFilter, "searchFilter must not be null.");
Assert.notNull(searchBase, "searchBase must not be null (an empty string is acceptable).");
this.searchFilter = searchFilter;
this.initialDirContextFactory = initialDirContextFactory;
this.contextSource = contextSource;
this.searchBase = searchBase;
if (searchBase.length() == 0) {
logger.info("SearchBase not set. Searches will be performed from the root: "
+ initialDirContextFactory.getRootDn());
+ contextSource.getBaseLdapPath());
}
}
@ -104,11 +104,10 @@ public class FilterBasedLdapUserSearch implements LdapUserSearch {
*/
public DirContextOperations searchForUser(String username) {
if (logger.isDebugEnabled()) {
logger.debug("Searching for user '" + username + "', with user search "
+ this.toString());
logger.debug("Searching for user '" + username + "', with user search " + this.toString());
}
SpringSecurityLdapTemplate template = new SpringSecurityLdapTemplate(initialDirContextFactory);
SpringSecurityLdapTemplate template = new SpringSecurityLdapTemplate(contextSource);
template.setSearchControls(searchControls);

View File

@ -16,13 +16,13 @@
package org.springframework.security.providers.ldap.authenticator;
import org.springframework.security.SpringSecurityMessageSource;
import org.springframework.security.ldap.InitialDirContextFactory;
import org.springframework.security.ldap.LdapUserSearch;
import org.springframework.security.providers.ldap.LdapAuthenticator;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.ldap.core.ContextSource;
import org.springframework.util.Assert;
import java.text.MessageFormat;
@ -40,18 +40,12 @@ import java.util.List;
public abstract class AbstractLdapAuthenticator implements LdapAuthenticator, InitializingBean, MessageSourceAware {
//~ Instance fields ================================================================================================
private InitialDirContextFactory initialDirContextFactory;
private ContextSource contextSource;
/** Optional search object which can be used to locate a user when a simple DN match isn't sufficient */
private LdapUserSearch userSearch;
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
/**
* The suffix to be added to the DN patterns, worked out internally from the root DN of the configured
* InitialDirContextFactory.
*/
private String dnSuffix = "";
/** The attributes which will be retrieved from the directory. Null means all attributes */
private String[] userAttributes = null;
@ -62,12 +56,13 @@ public abstract class AbstractLdapAuthenticator implements LdapAuthenticator, In
//~ Constructors ===================================================================================================
/**
* Create an initialized instance to the {@link InitialDirContextFactory} provided.
* Create an initialized instance with the {@link ContextSource} provided.
*
* @param initialDirContextFactory
* @param contextSource
*/
public AbstractLdapAuthenticator(InitialDirContextFactory initialDirContextFactory) {
this.setInitialDirContextFactory(initialDirContextFactory);
public AbstractLdapAuthenticator(ContextSource contextSource) {
Assert.notNull(contextSource, "contextSource must not be null.");
this.contextSource = contextSource;
}
//~ Methods ========================================================================================================
@ -77,24 +72,8 @@ public abstract class AbstractLdapAuthenticator implements LdapAuthenticator, In
"Either an LdapUserSearch or DN pattern (or both) must be supplied.");
}
/**
* Set the {@link InitialDirContextFactory} and initialize this instance from its data.
*
* @param initialDirContextFactory
*/
private void setInitialDirContextFactory(InitialDirContextFactory initialDirContextFactory) {
Assert.notNull(initialDirContextFactory, "initialDirContextFactory must not be null.");
this.initialDirContextFactory = initialDirContextFactory;
String rootDn = initialDirContextFactory.getRootDn();
if (rootDn.length() > 0) {
dnSuffix = "," + rootDn;
}
}
protected InitialDirContextFactory getInitialDirContextFactory() {
return initialDirContextFactory;
protected ContextSource getContextSource() {
return contextSource;
}
public String[] getUserAttributes() {
@ -102,9 +81,7 @@ public abstract class AbstractLdapAuthenticator implements LdapAuthenticator, In
}
/**
* Builds list of possible DNs for the user, worked out from the <tt>userDnPatterns</tt> property. The
* returned value includes the root DN of the provider URL used to configure the
* <tt>InitialDirContextfactory</tt>.
* Builds list of possible DNs for the user, worked out from the <tt>userDnPatterns</tt> property.
*
* @param username the user's login name
*
@ -120,7 +97,7 @@ public abstract class AbstractLdapAuthenticator implements LdapAuthenticator, In
synchronized (userDnFormat) {
for (int i = 0; i < userDnFormat.length; i++) {
userDns.add(userDnFormat[i].format(args) + dnSuffix);
userDns.add(userDnFormat[i].format(args));
}
}
@ -150,8 +127,7 @@ public abstract class AbstractLdapAuthenticator implements LdapAuthenticator, In
* Sets the pattern which will be used to supply a DN for the user. The pattern should be the name relative
* to the root DN. The pattern argument {0} will contain the username. An example would be "cn={0},ou=people".
*
* @param dnPattern the array of patterns which will be tried when obtaining a username
* to a DN.
* @param dnPattern the array of patterns which will be tried when converting a username to a DN.
*/
public void setUserDnPatterns(String[] dnPattern) {
Assert.notNull(dnPattern, "The array of DN patterns cannot be set to null");

View File

@ -15,20 +15,20 @@
package org.springframework.security.providers.ldap.authenticator;
import org.springframework.security.BadCredentialsException;
import org.springframework.security.Authentication;
import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
import org.springframework.security.ldap.InitialDirContextFactory;
import org.springframework.security.BadCredentialsException;
import org.springframework.security.ldap.SpringSecurityContextSource;
import org.springframework.security.ldap.SpringSecurityLdapTemplate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
import org.springframework.dao.DataAccessException;
import org.springframework.ldap.core.ContextSource;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.DistinguishedName;
import org.springframework.util.Assert;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import javax.naming.directory.DirContext;
import java.util.Iterator;
@ -49,12 +49,14 @@ public class BindAuthenticator extends AbstractLdapAuthenticator {
//~ Constructors ===================================================================================================
/**
* Create an initialized instance to the {@link InitialDirContextFactory} provided.
* Create an initialized instance using the {@link SpringSecurityContextSource} provided.
*
* @param contextSource the SpringSecurityContextSource instance against which bind operations will be
* performed.
*
* @param initialDirContextFactory
*/
public BindAuthenticator(InitialDirContextFactory initialDirContextFactory) {
super(initialDirContextFactory);
public BindAuthenticator(SpringSecurityContextSource contextSource) {
super(contextSource);
}
//~ Methods ========================================================================================================
@ -91,7 +93,7 @@ public class BindAuthenticator extends AbstractLdapAuthenticator {
private DirContextOperations bindWithDn(String userDn, String username, String password) {
SpringSecurityLdapTemplate template = new SpringSecurityLdapTemplate(
new BindWithSpecificDnContextSource(getInitialDirContextFactory(), userDn, password));
new BindWithSpecificDnContextSource((SpringSecurityContextSource) getContextSource(), userDn, password));
try {
return template.retrieveEntry(userDn, getUserAttributes());
@ -110,25 +112,26 @@ public class BindAuthenticator extends AbstractLdapAuthenticator {
* Allows subclasses to inspect the exception thrown by an attempt to bind with a particular DN.
* The default implementation just reports the failure to the debug log.
*/
void handleBindException(String userDn, String username, Throwable cause) {
protected void handleBindException(String userDn, String username, Throwable cause) {
if (logger.isDebugEnabled()) {
logger.debug("Failed to bind as " + userDn + ": " + cause);
}
}
private class BindWithSpecificDnContextSource implements ContextSource {
private InitialDirContextFactory ctxFactory;
private String userDn;
private SpringSecurityContextSource ctxFactory;
DistinguishedName userDn;
private String password;
public BindWithSpecificDnContextSource(InitialDirContextFactory ctxFactory, String userDn, String password) {
public BindWithSpecificDnContextSource(SpringSecurityContextSource ctxFactory, String userDn, String password) {
this.ctxFactory = ctxFactory;
this.userDn = userDn;
this.userDn = new DistinguishedName(userDn);
this.userDn.prepend(ctxFactory.getBaseLdapPath());
this.password = password;
}
public DirContext getReadOnlyContext() throws DataAccessException {
return ctxFactory.newInitialDirContext(userDn, password);
return ctxFactory.getReadWriteContext(userDn.toString(), password);
}
public DirContext getReadWriteContext() throws DataAccessException {

View File

@ -15,24 +15,21 @@
package org.springframework.security.providers.ldap.authenticator;
import org.springframework.security.BadCredentialsException;
import org.springframework.security.Authentication;
import org.springframework.security.ldap.InitialDirContextFactory;
import org.springframework.security.ldap.SpringSecurityLdapTemplate;
import org.springframework.security.BadCredentialsException;
import org.springframework.security.ldap.LdapUtils;
import org.springframework.security.providers.encoding.PasswordEncoder;
import org.springframework.security.ldap.SpringSecurityLdapTemplate;
import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
import org.springframework.security.providers.encoding.PasswordEncoder;
import org.springframework.security.userdetails.UsernameNotFoundException;
import org.springframework.ldap.NameNotFoundException;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.support.BaseLdapPathContextSource;
import org.springframework.util.Assert;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.Assert;
import org.springframework.ldap.core.DirContextOperations;
import java.util.Iterator;
@ -65,8 +62,8 @@ public final class PasswordComparisonAuthenticator extends AbstractLdapAuthentic
//~ Constructors ===================================================================================================
public PasswordComparisonAuthenticator(InitialDirContextFactory initialDirContextFactory) {
super(initialDirContextFactory);
public PasswordComparisonAuthenticator(BaseLdapPathContextSource contextSource) {
super(contextSource);
}
//~ Methods ========================================================================================================
@ -82,13 +79,14 @@ public final class PasswordComparisonAuthenticator extends AbstractLdapAuthentic
Iterator dns = getUserDns(username).iterator();
SpringSecurityLdapTemplate ldapTemplate = new SpringSecurityLdapTemplate(getInitialDirContextFactory());
SpringSecurityLdapTemplate ldapTemplate = new SpringSecurityLdapTemplate(getContextSource());
while (dns.hasNext() && user == null) {
final String userDn = (String) dns.next();
if (ldapTemplate.nameExists(userDn)) {
try {
user = ldapTemplate.retrieveEntry(userDn, getUserAttributes());
} catch (NameNotFoundException ignore) {
}
}

View File

@ -17,24 +17,20 @@ package org.springframework.security.providers.ldap.populator;
import org.springframework.security.GrantedAuthority;
import org.springframework.security.GrantedAuthorityImpl;
import org.springframework.security.ldap.InitialDirContextFactory;
import org.springframework.security.ldap.SpringSecurityLdapTemplate;
import org.springframework.security.providers.ldap.LdapAuthoritiesPopulator;
import org.springframework.ldap.core.ContextSource;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.util.Assert;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.Assert;
import org.springframework.ldap.core.DirContextOperations;
import javax.naming.directory.SearchControls;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import javax.naming.directory.SearchControls;
/**
* The default strategy for obtaining user role information from the directory.
@ -73,7 +69,7 @@ import javax.naming.directory.SearchControls;
* <pre>
* &lt;bean id="ldapAuthoritiesPopulator"
* class="org.springframework.security.providers.ldap.populator.DefaultLdapAuthoritiesPopulator">
* &lt;constructor-arg>&lt;ref local="initialDirContextFactory"/>&lt;/constructor-arg>
* &lt;constructor-arg>&lt;ref local="contextSource"/>&lt;/constructor-arg>
* &lt;constructor-arg>&lt;value>ou=groups&lt;/value>&lt;/constructor-arg>
* &lt;property name="groupRoleAttribute">&lt;value>ou&lt;/value>&lt;/property>
* &lt;!-- the following properties are shown with their default values -->
@ -104,10 +100,8 @@ public class DefaultLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator
*/
private GrantedAuthority defaultRole = null;
/**
* An initial context factory is only required if searching for groups is required.
*/
private InitialDirContextFactory initialDirContextFactory = null;
private ContextSource contextSource = null;
private SpringSecurityLdapTemplate ldapTemplate;
/**
@ -145,12 +139,12 @@ public class DefaultLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator
* Constructor for group search scenarios. <tt>userRoleAttributes</tt> may still be
* set as a property.
*
* @param initialDirContextFactory supplies the contexts used to search for user roles.
* @param contextSource supplies the contexts used to search for user roles.
* @param groupSearchBase if this is an empty string the search will be performed from the root DN of the
* context factory.
*/
public DefaultLdapAuthoritiesPopulator(InitialDirContextFactory initialDirContextFactory, String groupSearchBase) {
this.setInitialDirContextFactory(initialDirContextFactory);
public DefaultLdapAuthoritiesPopulator(ContextSource contextSource, String groupSearchBase) {
this.setContextSource(contextSource);
this.setGroupSearchBase(groupSearchBase);
}
@ -232,20 +226,20 @@ public class DefaultLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator
return authorities;
}
protected InitialDirContextFactory getInitialDirContextFactory() {
return initialDirContextFactory;
protected ContextSource getContextSource() {
return contextSource;
}
/**
* Set the {@link InitialDirContextFactory}
* Set the {@link ContextSource}
*
* @param initialDirContextFactory supplies the contexts used to search for user roles.
* @param contextSource supplies the contexts used to search for user roles.
*/
private void setInitialDirContextFactory(InitialDirContextFactory initialDirContextFactory) {
Assert.notNull(initialDirContextFactory, "InitialDirContextFactory must not be null");
this.initialDirContextFactory = initialDirContextFactory;
private void setContextSource(ContextSource contextSource) {
Assert.notNull(contextSource, "contextSource must not be null");
this.contextSource = contextSource;
ldapTemplate = new SpringSecurityLdapTemplate(initialDirContextFactory);
ldapTemplate = new SpringSecurityLdapTemplate(contextSource);
ldapTemplate.setSearchControls(searchControls);
}
@ -259,8 +253,7 @@ public class DefaultLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator
Assert.notNull(groupSearchBase, "The groupSearchBase (name to search under), must not be null.");
this.groupSearchBase = groupSearchBase;
if (groupSearchBase.length() == 0) {
logger.info("groupSearchBase is empty. Searches will be performed from the root: "
+ getInitialDirContextFactory().getRootDn());
logger.info("groupSearchBase is empty. Searches will be performed from the context source base");
}
}

View File

@ -14,42 +14,46 @@
*/
package org.springframework.security.userdetails.ldap;
import org.springframework.security.userdetails.UserDetails;
import org.springframework.security.userdetails.UsernameNotFoundException;
import org.springframework.security.userdetails.UserDetailsManager;
import org.springframework.security.ldap.LdapUtils;
import org.springframework.security.GrantedAuthority;
import org.springframework.security.GrantedAuthorityImpl;
import org.springframework.security.Authentication;
import org.springframework.security.BadCredentialsException;
import org.springframework.security.GrantedAuthority;
import org.springframework.security.GrantedAuthorityImpl;
import org.springframework.security.context.SecurityContextHolder;
import org.springframework.security.ldap.LdapUsernameToDnMapper;
import org.springframework.security.ldap.LdapUtils;
import org.springframework.security.ldap.DefaultLdapUsernameToDnMapper;
import org.springframework.security.userdetails.UserDetails;
import org.springframework.security.userdetails.UserDetailsManager;
import org.springframework.security.userdetails.UsernameNotFoundException;
import org.springframework.dao.DataAccessException;
import org.springframework.util.Assert;
import org.springframework.ldap.core.DistinguishedName;
import org.springframework.ldap.core.AttributesMapper;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.core.AttributesMapperCallbackHandler;
import org.springframework.ldap.core.ContextExecutor;
import org.springframework.ldap.core.ContextSource;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.ContextExecutor;
import org.springframework.ldap.core.DistinguishedName;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.core.SearchExecutor;
import org.springframework.ldap.core.AttributesMapperCallbackHandler;
import org.springframework.util.Assert;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import javax.naming.ldap.LdapContext;
import javax.naming.Context;
import javax.naming.NameNotFoundException;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.NameNotFoundException;
import javax.naming.directory.Attributes;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.DirContext;
import javax.naming.directory.ModificationItem;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.SearchControls;
import java.util.*;
import javax.naming.ldap.LdapContext;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
/**
* An Ldap implementation of UserDetailsManager.
@ -71,13 +75,15 @@ import java.util.*;
public class LdapUserDetailsManager implements UserDetailsManager {
private final Log logger = LogFactory.getLog(LdapUserDetailsManager.class);
/** The DN under which users entries are stored */
private DistinguishedName userDnBase = new DistinguishedName("cn=users");
/**
* The strategy for mapping usernames to LDAP distinguished names.
* This will be used when building DNs for creating new users etc.
*/
LdapUsernameToDnMapper usernameMapper = new DefaultLdapUsernameToDnMapper("cn=users", "uid");
/** The DN under which groups are stored */
private DistinguishedName groupSearchBase = new DistinguishedName("cn=groups");
/** The attribute which contains the user login name, and which is used by default to build the DN for new users */
private String usernameAttributeName = "uid";
/** Password attribute name */
private String passwordAttributeName = "userPassword";
@ -120,7 +126,7 @@ public class LdapUserDetailsManager implements UserDetailsManager {
}
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
DistinguishedName dn = buildDn(username);
DistinguishedName dn = usernameMapper.buildDn(username);
GrantedAuthority[] authorities = getUserAuthorities(dn, username);
logger.debug("Loading user '"+ username + "' with DN '" + dn + "'");
@ -130,12 +136,12 @@ public class LdapUserDetailsManager implements UserDetailsManager {
return userDetailsMapper.mapUserFromContext(userCtx, username, authorities);
}
private UserContext loadUserAsContext(final DistinguishedName dn, final String username) {
return (UserContext) template.executeReadOnly(new ContextExecutor() {
private DirContextAdapter loadUserAsContext(final DistinguishedName dn, final String username) {
return (DirContextAdapter) template.executeReadOnly(new ContextExecutor() {
public Object executeWithContext(DirContext ctx) throws NamingException {
try {
Attributes attrs = ctx.getAttributes(dn, attributesToRetrieve);
return new UserContext(attrs, LdapUtils.getFullDn(dn, ctx));
return new DirContextAdapter(attrs, LdapUtils.getFullDn(dn, ctx));
} catch(NameNotFoundException notFound) {
throw new UsernameNotFoundException("User " + username + " not found", notFound);
}
@ -163,7 +169,7 @@ public class LdapUserDetailsManager implements UserDetailsManager {
logger.debug("Changing password for user '"+ username);
final DistinguishedName dn = buildDn(username);
final DistinguishedName dn = usernameMapper.buildDn(username);
final ModificationItem[] passwordChange = new ModificationItem[] {
new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute(passwordAttributeName, newPassword))
};
@ -227,7 +233,7 @@ public class LdapUserDetailsManager implements UserDetailsManager {
public void createUser(UserDetails user) {
DirContextAdapter ctx = new DirContextAdapter();
copyToContext(user, ctx);
DistinguishedName dn = buildDn(user.getUsername());
DistinguishedName dn = usernameMapper.buildDn(user.getUsername());
// Check for any existing authorities which might be set for this DN
GrantedAuthority[] authorities = getUserAuthorities(dn, user.getUsername());
@ -244,13 +250,13 @@ public class LdapUserDetailsManager implements UserDetailsManager {
public void updateUser(UserDetails user) {
// Assert.notNull(attributesToRetrieve, "Configuration must specify a list of attributes in order to use update.");
DistinguishedName dn = buildDn(user.getUsername());
DistinguishedName dn = usernameMapper.buildDn(user.getUsername());
logger.debug("Updating user '"+ user.getUsername() + "' with DN '" + dn + "'");
GrantedAuthority[] authorities = getUserAuthorities(dn, user.getUsername());
UserContext ctx = loadUserAsContext(dn, user.getUsername());
DirContextAdapter ctx = loadUserAsContext(dn, user.getUsername());
ctx.setUpdateMode(true);
copyToContext(user, ctx);
@ -275,13 +281,13 @@ public class LdapUserDetailsManager implements UserDetailsManager {
}
public void deleteUser(String username) {
DistinguishedName dn = buildDn(username);
DistinguishedName dn = usernameMapper.buildDn(username);
removeAuthorities(dn, getUserAuthorities(dn, username));
template.unbind(dn);
}
public boolean userExists(String username) {
DistinguishedName dn = buildDn(username);
DistinguishedName dn = usernameMapper.buildDn(username);
try {
Object obj = template.lookup(dn);
@ -294,25 +300,6 @@ public class LdapUserDetailsManager implements UserDetailsManager {
}
}
/**
* Constructs a DN from a username.
* <p>
* The default implementation appends a name component to the <tt>userDnBase</tt> context using the
* <tt>usernameAttributeName</tt> property. So if the <tt>uid</tt> attribute is used to store the username, and the
* base DN is <tt>cn=users</tt> and we are creating a new user called "sam", then the DN will be
* <tt>uid=sam,cn=users</tt>.
*
* @param username the user name used for authentication.
* @return the corresponding DN, relative to the base context.
*/
protected DistinguishedName buildDn(String username) {
DistinguishedName dn = new DistinguishedName(userDnBase);
dn.add(usernameAttributeName, username);
return dn;
}
/**
* Creates a DN from a group name.
*
@ -365,8 +352,8 @@ public class LdapUserDetailsManager implements UserDetailsManager {
return group;
}
public void setUsernameAttributeName(String usernameAttributeName) {
this.usernameAttributeName = usernameAttributeName;
public void setUsernameMapper(LdapUsernameToDnMapper usernameMapper) {
this.usernameMapper = usernameMapper;
}
public void setPasswordAttributeName(String passwordAttributeName) {
@ -381,10 +368,6 @@ public class LdapUserDetailsManager implements UserDetailsManager {
this.groupRoleAttributeName = groupRoleAttributeName;
}
public void setUserDnBase(String userDnBase) {
this.userDnBase = new DistinguishedName(userDnBase);
}
public void setAttributesToRetrieve(String[] attributesToRetrieve) {
Assert.notNull(attributesToRetrieve);
this.attributesToRetrieve = attributesToRetrieve;
@ -411,18 +394,4 @@ public class LdapUserDetailsManager implements UserDetailsManager {
public void setRoleMapper(AttributesMapper roleMapper) {
this.roleMapper = roleMapper;
}
/**
* This class allows us to set the <tt>updateMode</tt> property of DirContextAdapter when updating existing users.
* TODO: No longer needed as of Ldap 1.2.
*/
private static class UserContext extends DirContextAdapter {
public UserContext(Attributes pAttrs, Name dn) {
super(pAttrs, dn);
}
public void setUpdateMode(boolean mode) {
super.setUpdateMode(mode);
}
}
}

View File

@ -1,12 +1,12 @@
package org.springframework.security.config;
import org.springframework.security.ldap.InitialDirContextFactory;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.core.support.BaseLdapPathContextSource;
import org.junit.AfterClass;
import static org.junit.Assert.*;
import org.junit.BeforeClass;
import org.junit.AfterClass;
import org.junit.Test;
@ -32,14 +32,13 @@ public class LdapBeanDefinitionParserTests {
@Test
public void testContextContainsExpectedBeansAndData() {
InitialDirContextFactory idcf = (InitialDirContextFactory) appContext.getBean("contextSource");
BaseLdapPathContextSource idcf = (BaseLdapPathContextSource) appContext.getBean("contextSource");
assertEquals("dc=springframework,dc=org", idcf.getRootDn());
// assertEquals("dc=springframework, dc=org", idcf.getBaseLdapPathAsString());
// Check data is loaded
LdapTemplate template = new LdapTemplate(idcf);
template.lookup("uid=ben,ou=people");
}
}

View File

@ -107,11 +107,11 @@ public abstract class AbstractLdapIntegrationTests {
loader.execute();
} finally {
ctx.close();
}
}
}
public ContextSource getContextSource() {
return (ContextSource) appContext.getBean("contextSource");
public SpringSecurityContextSource getContextSource() {
return (SpringSecurityContextSource) appContext.getBean("contextSource");
}
/**

View File

@ -0,0 +1,15 @@
package org.springframework.security.ldap;
import org.junit.Test;
/**
* @author Luke Taylor
* @version $Id$
*/
public class DefaultSpringSecurityContextSourceTests {
@Test
public void instantiationSucceeds() {
new DefaultSpringSecurityContextSource("ldap://blah:789/dc=springframework,dc=org");
}
}

View File

@ -71,6 +71,7 @@ public class LdapUtilsTests extends MockObjectTestCase {
public void testRootDnsAreParsedFromUrlsCorrectly() {
assertEquals("", LdapUtils.parseRootDnFromUrl("ldap://monkeymachine"));
assertEquals("", LdapUtils.parseRootDnFromUrl("ldap://monkeymachine:11389"));
assertEquals("", LdapUtils.parseRootDnFromUrl("ldap://monkeymachine/"));
assertEquals("", LdapUtils.parseRootDnFromUrl("ldap://monkeymachine.co.uk/"));
assertEquals("dc=springframework,dc=org",
@ -80,5 +81,7 @@ public class LdapUtilsTests extends MockObjectTestCase {
LdapUtils.parseRootDnFromUrl("ldap://monkeymachine/dc=springframework,dc=org"));
assertEquals("dc=springframework,dc=org/ou=blah",
LdapUtils.parseRootDnFromUrl("ldap://monkeymachine.co.uk/dc=springframework,dc=org/ou=blah"));
assertEquals("dc=springframework,dc=org/ou=blah",
LdapUtils.parseRootDnFromUrl("ldap://monkeymachine.co.uk:389/dc=springframework,dc=org/ou=blah"));
}
}

View File

@ -16,6 +16,7 @@
package org.springframework.security.ldap;
import org.springframework.dao.DataAccessException;
import org.springframework.ldap.core.DistinguishedName;
import javax.naming.directory.DirContext;
@ -25,7 +26,7 @@ import javax.naming.directory.DirContext;
* @author Luke Taylor
* @version $Id$
*/
public class MockInitialDirContextFactory implements InitialDirContextFactory {
public class MockSpringSecurityContextSource implements SpringSecurityContextSource {
//~ Instance fields ================================================================================================
private DirContext ctx;
@ -33,25 +34,13 @@ public class MockInitialDirContextFactory implements InitialDirContextFactory {
//~ Constructors ===================================================================================================
public MockInitialDirContextFactory(DirContext ctx, String baseDn) {
public MockSpringSecurityContextSource(DirContext ctx, String baseDn) {
this.baseDn = baseDn;
this.ctx = ctx;
}
//~ Methods ========================================================================================================
public String getRootDn() {
return baseDn;
}
public DirContext newInitialDirContext() {
return ctx;
}
public DirContext newInitialDirContext(String username, String password) {
return ctx;
}
public DirContext getReadOnlyContext() throws DataAccessException {
return ctx;
}
@ -59,4 +48,16 @@ public class MockInitialDirContextFactory implements InitialDirContextFactory {
public DirContext getReadWriteContext() throws DataAccessException {
return ctx;
}
public DirContext getReadWriteContext(String userDn, Object credentials) {
return ctx;
}
public DistinguishedName getBaseLdapPath() {
return new DistinguishedName(baseDn);
}
public String getBaseLdapPathAsString() {
return getBaseLdapPath().toString();
}
}

View File

@ -44,33 +44,33 @@ public class SpringSecurityLdapTemplateTests extends AbstractLdapIntegrationTest
@Test
public void testCompareOfCorrectValueSucceeds() {
assertTrue(template.compare("uid=bob,ou=people,dc=springframework,dc=org", "uid", "bob"));
assertTrue(template.compare("uid=bob,ou=people", "uid", "bob"));
}
@Test
public void testCompareOfCorrectByteValueSucceeds() {
assertTrue(template.compare("uid=bob,ou=people,dc=springframework,dc=org", "userPassword", LdapUtils.getUtf8Bytes("bobspassword")));
assertTrue(template.compare("uid=bob,ou=people", "userPassword", LdapUtils.getUtf8Bytes("bobspassword")));
}
@Test
public void testCompareOfWrongByteValueFails() {
assertFalse(template.compare("uid=bob,ou=people,dc=springframework,dc=org", "userPassword", LdapUtils.getUtf8Bytes("wrongvalue")));
assertFalse(template.compare("uid=bob,ou=people", "userPassword", LdapUtils.getUtf8Bytes("wrongvalue")));
}
@Test
public void testCompareOfWrongValueFails() {
assertFalse(template.compare("uid=bob,ou=people,dc=springframework,dc=org", "uid", "wrongvalue"));
assertFalse(template.compare("uid=bob,ou=people", "uid", "wrongvalue"));
}
@Test
public void testNameExistsForInValidNameFails() {
assertFalse(template.nameExists("ou=doesntexist,dc=springframework,dc=org"));
}
@Test
public void testNameExistsForValidNameSucceeds() {
assertTrue(template.nameExists("ou=groups,dc=springframework,dc=org"));
}
// @Test
// public void testNameExistsForInValidNameFails() {
// assertFalse(template.nameExists("ou=doesntexist,dc=springframework,dc=org"));
// }
//
// @Test
// public void testNameExistsForValidNameSucceeds() {
// assertTrue(template.nameExists("ou=groups,dc=springframework,dc=org"));
// }
@Test
public void testNamingExceptionIsTranslatedCorrectly() {

View File

@ -15,16 +15,15 @@
package org.springframework.security.ldap.search;
import org.springframework.security.ldap.DefaultInitialDirContextFactory;
import org.springframework.security.ldap.AbstractLdapIntegrationTests;
import org.springframework.security.userdetails.UsernameNotFoundException;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.ldap.core.DirContextOperations;
import org.junit.Test;
import org.springframework.ldap.core.DistinguishedName;
import org.springframework.ldap.core.support.BaseLdapPathContextSource;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
/**
* Tests for FilterBasedLdapUserSearch.
@ -35,13 +34,13 @@ import static org.junit.Assert.*;
public class FilterBasedLdapUserSearchTests extends AbstractLdapIntegrationTests {
//~ Instance fields ================================================================================================
private DefaultInitialDirContextFactory dirCtxFactory;
private BaseLdapPathContextSource dirCtxFactory;
//~ Methods ========================================================================================================
public void onSetUp() throws Exception {
super.onSetUp();
dirCtxFactory = (DefaultInitialDirContextFactory) getContextSource();
dirCtxFactory = getContextSource();
}
@Test
@ -54,8 +53,7 @@ public class FilterBasedLdapUserSearchTests extends AbstractLdapIntegrationTests
DirContextOperations bob = locator.searchForUser("bob");
assertEquals("bob", bob.getStringAttribute("uid"));
// name is wrong with embedded apacheDS
// assertEquals("uid=bob,ou=people,dc=springframework,dc=org", bob.getDn());
assertEquals(new DistinguishedName("uid=bob,ou=people"), bob.getDn());
}
// Try some funny business with filters.
@ -71,24 +69,16 @@ public class FilterBasedLdapUserSearchTests extends AbstractLdapIntegrationTests
// assertEquals("uid=ben,ou=people,"+ROOT_DN, ben.getDn());
}
@Test
@Test(expected=IncorrectResultSizeDataAccessException.class)
public void testFailsOnMultipleMatches() {
FilterBasedLdapUserSearch locator = new FilterBasedLdapUserSearch("ou=people", "(cn=*)", dirCtxFactory);
try {
locator.searchForUser("Ignored");
fail("Expected exception for multiple search matches.");
} catch (IncorrectResultSizeDataAccessException expected) {}
locator.searchForUser("Ignored");
}
@Test
@Test(expected=UsernameNotFoundException.class)
public void testSearchForInvalidUserFails() {
FilterBasedLdapUserSearch locator = new FilterBasedLdapUserSearch("ou=people", "(uid={0})", dirCtxFactory);
try {
locator.searchForUser("Joe");
fail("Expected UsernameNotFoundException for non-existent user.");
} catch (UsernameNotFoundException expected) {}
locator.searchForUser("Joe");
}
@Test
@ -100,7 +90,7 @@ public class FilterBasedLdapUserSearchTests extends AbstractLdapIntegrationTests
DirContextOperations ben = locator.searchForUser("Ben Alex");
assertEquals("ben", ben.getStringAttribute("uid"));
// assertEquals("uid=ben,ou=people,dc=springframework,dc=org", ben.getDn());
assertEquals(new DistinguishedName("uid=ben,ou=people"), ben.getDn());
}
// TODO: Add test with non-uid username

View File

@ -15,19 +15,17 @@
package org.springframework.security.providers.ldap.authenticator;
import org.springframework.security.SpringSecurityMessageSource;
import org.springframework.security.BadCredentialsException;
import org.springframework.security.Authentication;
import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
import org.springframework.security.BadCredentialsException;
import org.springframework.security.SpringSecurityMessageSource;
import org.springframework.security.ldap.AbstractLdapIntegrationTests;
import org.springframework.security.ldap.InitialDirContextFactory;
import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.DistinguishedName;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.DistinguishedName;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import org.junit.Test;
/**
@ -41,16 +39,16 @@ public class BindAuthenticatorTests extends AbstractLdapIntegrationTests {
private BindAuthenticator authenticator;
private Authentication bob;
private Authentication ben;
// private Authentication ben;
//~ Methods ========================================================================================================
public void onSetUp() {
authenticator = new BindAuthenticator((InitialDirContextFactory) getContextSource());
authenticator = new BindAuthenticator(getContextSource());
authenticator.setMessageSource(new SpringSecurityMessageSource());
bob = new UsernamePasswordAuthenticationToken("bob", "bobspassword");
ben = new UsernamePasswordAuthenticationToken("ben", "benspassword");
// ben = new UsernamePasswordAuthenticationToken("ben", "benspassword");
}
@ -74,7 +72,7 @@ public class BindAuthenticatorTests extends AbstractLdapIntegrationTests {
@Test
public void testAuthenticationWithUserSearch() throws Exception {
DirContextAdapter ctx = new DirContextAdapter(new DistinguishedName("uid=bob,ou=people,dc=springframework,dc=org"));
DirContextAdapter ctx = new DirContextAdapter(new DistinguishedName("uid=bob,ou=people"));
authenticator.setUserSearch(new MockUserSearch(ctx));
authenticator.afterPropertiesSet();
@ -94,6 +92,6 @@ public class BindAuthenticatorTests extends AbstractLdapIntegrationTests {
@Test
public void testUserDnPatternReturnsCorrectDn() {
authenticator.setUserDnPatterns(new String[] {"cn={0},ou=people"});
assertEquals("cn=Joe,ou=people," + ((InitialDirContextFactory)getContextSource()).getRootDn(), authenticator.getUserDns("Joe").get(0));
assertEquals("cn=Joe,ou=people", authenticator.getUserDns("Joe").get(0));
}
}

View File

@ -15,7 +15,7 @@
package org.springframework.security.providers.ldap.authenticator;
import org.springframework.security.ldap.MockInitialDirContextFactory;
import org.springframework.security.ldap.MockSpringSecurityContextSource;
import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
import org.jmock.Mock;
@ -40,15 +40,15 @@ public class PasswordComparisonAuthenticatorMockTests extends MockObjectTestCase
BasicAttributes attrs = new BasicAttributes();
attrs.put(new BasicAttribute("uid", "bob"));
PasswordComparisonAuthenticator authenticator = new PasswordComparisonAuthenticator(new MockInitialDirContextFactory(
(DirContext) mockCtx.proxy(), "dc=springframework,dc=org"));
PasswordComparisonAuthenticator authenticator = new PasswordComparisonAuthenticator(new MockSpringSecurityContextSource(
(DirContext) mockCtx.proxy(), ""));
authenticator.setUserDnPatterns(new String[] {"cn={0},ou=people"});
// Get the mock to return an empty attribute set
mockCtx.expects(atLeastOnce()).method("getNameInNamespace").will(returnValue("dc=springframework,dc=org"));
mockCtx.expects(once()).method("lookup").with(eq("cn=Bob, ou=people")).will(returnValue(true));
mockCtx.expects(once()).method("getAttributes").with(eq("cn=Bob, ou=people"), NULL)
// mockCtx.expects(atLeastOnce()).method("getNameInNamespace").will(returnValue("dc=springframework,dc=org"));
// mockCtx.expects(once()).method("lookup").with(eq("cn=Bob,ou=people")).will(returnValue(true));
mockCtx.expects(once()).method("getAttributes").with(eq("cn=Bob,ou=people"), NULL)
.will(returnValue(attrs));
// Setup a single return value (i.e. success)

View File

@ -19,7 +19,6 @@ import org.springframework.security.BadCredentialsException;
import org.springframework.security.Authentication;
import org.springframework.security.ldap.AbstractLdapIntegrationTests;
import org.springframework.security.ldap.InitialDirContextFactory;
import org.springframework.security.providers.encoding.PlaintextPasswordEncoder;
import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
@ -49,7 +48,7 @@ public class PasswordComparisonAuthenticatorTests extends AbstractLdapIntegratio
public void onSetUp() throws Exception {
super.onSetUp();
authenticator = new PasswordComparisonAuthenticator((InitialDirContextFactory) getContextSource());
authenticator = new PasswordComparisonAuthenticator(getContextSource());
authenticator.setUserDnPatterns(new String[] {"uid={0},ou=people"});
bob = new UsernamePasswordAuthenticationToken("bob", "bobspassword");
ben = new UsernamePasswordAuthenticationToken("ben", "benspassword");
@ -64,7 +63,7 @@ public class PasswordComparisonAuthenticatorTests extends AbstractLdapIntegratio
@Test
public void testFailedSearchGivesUserNotFoundException() throws Exception {
authenticator = new PasswordComparisonAuthenticator((InitialDirContextFactory) getContextSource());
authenticator = new PasswordComparisonAuthenticator(getContextSource());
assertTrue("User DN matches shouldn't be available", authenticator.getUserDns("Bob").isEmpty());
authenticator.setUserSearch(new MockUserSearch(null));
authenticator.afterPropertiesSet();
@ -164,7 +163,7 @@ public class PasswordComparisonAuthenticatorTests extends AbstractLdapIntegratio
@Test
public void testWithUserSearch() {
authenticator = new PasswordComparisonAuthenticator((InitialDirContextFactory) getContextSource());
authenticator = new PasswordComparisonAuthenticator(getContextSource());
assertTrue("User DN matches shouldn't be available", authenticator.getUserDns("Bob").isEmpty());
DirContextAdapter ctx = new DirContextAdapter(new DistinguishedName("uid=Bob,ou=people,dc=springframework,dc=org"));

View File

@ -18,7 +18,6 @@ package org.springframework.security.providers.ldap.populator;
import org.springframework.security.GrantedAuthority;
import org.springframework.security.ldap.AbstractLdapIntegrationTests;
import org.springframework.security.ldap.InitialDirContextFactory;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.DistinguishedName;
@ -42,7 +41,7 @@ public class DefaultLdapAuthoritiesPopulatorTests extends AbstractLdapIntegratio
public void onSetUp() throws Exception {
super.onSetUp();
populator = new DefaultLdapAuthoritiesPopulator((InitialDirContextFactory) getContextSource(), "ou=groups");
populator = new DefaultLdapAuthoritiesPopulator(getContextSource(), "ou=groups");
}

View File

@ -14,20 +14,20 @@
*/
package org.springframework.security.userdetails.ldap;
import org.springframework.security.ldap.SpringSecurityLdapTemplate;
import org.springframework.security.ldap.AbstractLdapIntegrationTests;
import org.springframework.security.userdetails.UserDetails;
import org.springframework.security.userdetails.UsernameNotFoundException;
import org.springframework.security.BadCredentialsException;
import org.springframework.security.GrantedAuthority;
import org.springframework.security.GrantedAuthorityImpl;
import org.springframework.security.BadCredentialsException;
import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
import org.springframework.security.context.SecurityContextHolder;
import org.springframework.security.ldap.AbstractLdapIntegrationTests;
import org.springframework.security.ldap.DefaultLdapUsernameToDnMapper;
import org.springframework.security.ldap.SpringSecurityLdapTemplate;
import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
import org.springframework.security.userdetails.UserDetails;
import org.springframework.security.userdetails.UsernameNotFoundException;
import org.springframework.ldap.core.DirContextAdapter;
import static org.junit.Assert.*;
import org.junit.After;
import static org.junit.Assert.*;
import org.junit.Test;
/**
@ -63,7 +63,7 @@ public class LdapUserDetailsManagerTests extends AbstractLdapIntegrationTests {
group.setAttributeValue("cn", "acrobats");
template.bind("cn=acrobats,ou=testgroups", group, null);
mgr.setUserDnBase("ou=testpeople");
mgr.setUsernameMapper(new DefaultLdapUsernameToDnMapper("ou=testpeople","uid"));
mgr.setGroupSearchBase("ou=testgroups");
mgr.setGroupRoleAttributeName("cn");
mgr.setGroupMemberAttributeName("member");
@ -88,7 +88,7 @@ public class LdapUserDetailsManagerTests extends AbstractLdapIntegrationTests {
@Test
public void testLoadUserByUsernameReturnsCorrectData() {
mgr.setUserDnBase("ou=people");
mgr.setUsernameMapper(new DefaultLdapUsernameToDnMapper("ou=people","uid"));
mgr.setGroupSearchBase("ou=groups");
UserDetails bob = mgr.loadUserByUsername("bob");
assertEquals("bob", bob.getUsername());
@ -111,7 +111,7 @@ public class LdapUserDetailsManagerTests extends AbstractLdapIntegrationTests {
@Test
public void testUserExistsReturnsTrueForValidUser() {
mgr.setUserDnBase("ou=people");
mgr.setUsernameMapper(new DefaultLdapUsernameToDnMapper("ou=people","uid"));
assertTrue(mgr.userExists("bob"));
}
@ -156,7 +156,7 @@ public class LdapUserDetailsManagerTests extends AbstractLdapIntegrationTests {
}
// Check that no authorities are left
assertEquals(0, mgr.getUserAuthorities(mgr.buildDn("don"), "don").length);
assertEquals(0, mgr.getUserAuthorities(mgr.usernameMapper.buildDn("don"), "don").length);
}
@Test
@ -175,7 +175,7 @@ public class LdapUserDetailsManagerTests extends AbstractLdapIntegrationTests {
mgr.changePassword("yossarianspassword", "yossariansnewpassword");
assertTrue(template.compare("uid=johnyossarian,ou=testpeople,dc=springframework,dc=org",
assertTrue(template.compare("uid=johnyossarian,ou=testpeople",
"userPassword", "yossariansnewpassword"));
}

View File

@ -3,18 +3,19 @@
*/
package org.springframework.security.ui.ntlm.ldap.authenticator;
import java.util.Iterator;
import org.springframework.security.BadCredentialsException;
import org.springframework.security.Authentication;
import org.springframework.security.ldap.InitialDirContextFactory;
import org.springframework.security.BadCredentialsException;
import org.springframework.security.ldap.SpringSecurityContextSource;
import org.springframework.security.ldap.SpringSecurityLdapTemplate;
import org.springframework.security.providers.ldap.authenticator.BindAuthenticator;
import org.springframework.security.ui.ntlm.NtlmUsernamePasswordAuthenticationToken;
import org.springframework.ldap.NameNotFoundException;
import org.springframework.ldap.core.DirContextOperations;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.NameNotFoundException;
import java.util.Iterator;
/**
* Loads the UserDetails if authentication was already performed by NTLM (indicated by the type of authentication
@ -31,8 +32,8 @@ public class NtlmAwareLdapAuthenticator extends BindAuthenticator {
//~ Constructors ===================================================================================================
public NtlmAwareLdapAuthenticator(InitialDirContextFactory initialDirContextFactory) {
super(initialDirContextFactory);
public NtlmAwareLdapAuthenticator(SpringSecurityContextSource contextSource) {
super(contextSource);
}
//~ Methods ========================================================================================================
@ -41,7 +42,7 @@ public class NtlmAwareLdapAuthenticator extends BindAuthenticator {
* Loads the user context information without binding.
*/
protected DirContextOperations loadUser(String aUserDn, String aUserName) {
SpringSecurityLdapTemplate template = new SpringSecurityLdapTemplate(getInitialDirContextFactory());
SpringSecurityLdapTemplate template = new SpringSecurityLdapTemplate(getContextSource());
try {
DirContextOperations user = template.retrieveEntry(aUserDn, getUserAttributes());

12
pom.xml
View File

@ -76,15 +76,19 @@
<repositories>
<repository>
<id>sourceforge.net</id>
<id>acegisnapshots</id>
<name>Acegi snapshot repository</name>
<url>
http://acegisecurity.sourceforge.net/repository/snapshots
</url>
<url>http://acegisecurity.sourceforge.net/repository/snapshots</url>
<releases>
<enabled>false</enabled>
</releases>
</repository>
<!-- TODO: Added for spring-ldap-1.2.1-SNAPSHOT -->
<repository>
<id>acegirepo</id>
<name>Acegi maven repository</name>
<url>http://acegisecurity.sourceforge.net/maven</url>
</repository>
<repository>
<id>spring-milestone</id>
<name>Springframework Maven Milestone Repository</name>

View File

@ -36,22 +36,22 @@
</property>
</bean>
<bean id="initialDirContextFactory" class="org.springframework.security.ldap.DefaultInitialDirContextFactory">
<bean id="contextSource" class="org.springframework.security.ldap.DefaultSpringSecurityContextSource">
<constructor-arg value="ldap://monkeymachine.co.uk:389/dc=springframework,dc=org"/>
<property name="managerDn" value="cn=manager,dc=springframework,dc=org" />
<property name="managerPassword" value="acegisecurity"/>
<property name="userDn" value="cn=manager,dc=springframework,dc=org" />
<property name="password" value="acegisecurity"/>
</bean>
<bean id="ldapAuthenticationProvider" class="org.springframework.security.providers.ldap.LdapAuthenticationProvider">
<constructor-arg>
<bean class="org.springframework.security.providers.ldap.authenticator.BindAuthenticator">
<constructor-arg><ref local="initialDirContextFactory"/></constructor-arg>
<constructor-arg><ref local="contextSource"/></constructor-arg>
<property name="userDnPatterns"><list><value>uid={0},ou=people</value></list></property>
</bean>
</constructor-arg>
<constructor-arg>
<bean class="org.springframework.security.providers.ldap.populator.DefaultLdapAuthoritiesPopulator">
<constructor-arg><ref local="initialDirContextFactory"/></constructor-arg>
<constructor-arg><ref local="contextSource"/></constructor-arg>
<constructor-arg><value>ou=groups</value></constructor-arg>
<property name="groupRoleAttribute"><value>ou</value></property>
</bean>