Java tutorial
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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.acegisecurity.ldap; import org.acegisecurity.AcegiMessageSource; import org.acegisecurity.BadCredentialsException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.context.MessageSource; import org.springframework.context.MessageSourceAware; import org.springframework.context.support.MessageSourceAccessor; import org.springframework.util.Assert; import java.util.Hashtable; import java.util.Map; import java.util.StringTokenizer; import javax.naming.CommunicationException; import javax.naming.Context; import javax.naming.NamingException; import javax.naming.OperationNotSupportedException; import javax.naming.ldap.InitialLdapContext; import javax.naming.directory.DirContext; import javax.naming.directory.InitialDirContext; /** * Encapsulates the information for connecting to an LDAP server and provides an access point for obtaining * <tt>DirContext</tt> references. * <p> * The directory location is configured using by setting the constructor argument * <tt>providerUrl</tt>. This should be in the form <tt>ldap://monkeymachine.co.uk:389/dc=acegisecurity,dc=org</tt>. * The Sun JNDI provider also supports lists of space-separated URLs, each of which will be tried in turn until a * connection is obtained. * </p> * <p>To obtain an initial context, the client calls the <tt>newInitialDirContext</tt> method. There are two * signatures - one with no arguments and one which allows binding with a specific username and password. * </p> * <p>The no-args version will bind anonymously unless a manager login has been configured using the properties * <tt>managerDn</tt> and <tt>managerPassword</tt>, in which case it will bind as the manager user.</p> * <p>Connection pooling is enabled by default for anonymous or manager connections, but not when binding as a * specific user.</p> * * @author Robert Sanders * @author Luke Taylor * @version $Id: DefaultInitialDirContextFactory.java 1784 2007-02-24 21:00:24Z luke_t $ * * @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 { //~ Static fields/initializers ===================================================================================== private static final Log logger = LogFactory.getLog(DefaultInitialDirContextFactory.class); private static final String CONNECTION_POOL_KEY = "com.sun.jndi.ldap.connect.pool"; private static final String AUTH_TYPE_NONE = "none"; //~ Instance fields ================================================================================================ /** Allows extra environment variables to be added at config time. */ private Map extraEnvVars = null; protected MessageSourceAccessor messages = AcegiMessageSource.getAccessor(); /** Type of authentication within LDAP; default is simple. */ private String authenticationType = "simple"; /** * The INITIAL_CONTEXT_FACTORY used to create the JNDI Factory. Default is * "com.sun.jndi.ldap.LdapCtxFactory"; you <b>should not</b> need to set this unless you have unusual needs. */ private String initialContextFactory = "com.sun.jndi.ldap.LdapCtxFactory"; /** * If your LDAP server does not allow anonymous searches then you will need to provide a "manager" user's * DN to log in with. */ private String managerDn = null; /** The manager user's password. */ private String managerPassword = "manager_password_not_set"; /** The LDAP url of the server (and root context) to connect to. */ private String providerUrl; /** * The root DN. This is worked out from the url. It is used by client classes when forming a full DN for * bind authentication (for example). */ private String rootDn = null; /** * Use the LDAP Connection pool; if true, then the LDAP environment property * "com.sun.jndi.ldap.connect.pool" is added to any other JNDI properties. */ private boolean useConnectionPool = true; /** Set to true for ldap v3 compatible servers */ private boolean useLdapContext = false; //~ Constructors =================================================================================================== /** * Create and initialize an instance to the LDAP url provided * * @param providerUrl a String of the form <code>ldap://localhost:389/base_dn<code> */ public DefaultInitialDirContextFactory(String providerUrl) { this.setProviderUrl(providerUrl); } //~ Methods ======================================================================================================== /** * Set the LDAP url * * @param providerUrl a String of the form <code>ldap://localhost:389/base_dn<code> */ private void setProviderUrl(String providerUrl) { Assert.hasLength(providerUrl, "An LDAP connection URL must be supplied."); this.providerUrl = providerUrl; StringTokenizer st = new StringTokenizer(providerUrl); // 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); 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"); } } // This doesn't necessarily hold for embedded servers. //Assert.isTrue(uri.getScheme().equals("ldap"), "Ldap URL must start with 'ldap://'"); } /** * Get the LDAP url * * @return the url */ private String getProviderUrl() { return providerUrl; } private InitialDirContext connect(Hashtable env) { if (logger.isDebugEnabled()) { Hashtable envClone = (Hashtable) env.clone(); if (envClone.containsKey(Context.SECURITY_CREDENTIALS)) { envClone.put(Context.SECURITY_CREDENTIALS, "******"); } logger.debug("Creating InitialDirContext with environment " + envClone); } try { return useLdapContext ? new InitialLdapContext(env, null) : new InitialDirContext(env); } catch (NamingException ne) { if ((ne instanceof javax.naming.AuthenticationException) || (ne instanceof OperationNotSupportedException)) { throw new BadCredentialsException( messages.getMessage("DefaultIntitalDirContextFactory.badCredentials", "Bad credentials"), ne); } if (ne instanceof CommunicationException) { throw new LdapDataAccessException( messages.getMessage("DefaultIntitalDirContextFactory.communicationFailure", "Unable to connect to LDAP server"), ne); } throw new LdapDataAccessException( messages.getMessage("DefaultIntitalDirContextFactory.unexpectedException", "Failed to obtain InitialDirContext due to unexpected exception"), ne); } } /** * Sets up the environment parameters for creating a new context. * * @return the Hashtable describing the base DirContext that will be created, minus the username/password if any. */ protected Hashtable getEnvironment() { Hashtable env = new Hashtable(); env.put(Context.SECURITY_AUTHENTICATION, authenticationType); env.put(Context.INITIAL_CONTEXT_FACTORY, initialContextFactory); env.put(Context.PROVIDER_URL, getProviderUrl()); if (useConnectionPool) { env.put(CONNECTION_POOL_KEY, "true"); } if ((extraEnvVars != null) && (extraEnvVars.size() > 0)) { env.putAll(extraEnvVars); } return env; } /** * Returns the root DN of the configured provider URL. For example, if the URL is * <tt>ldap://monkeymachine.co.uk:389/dc=acegisecurity,dc=org</tt> the value will be * <tt>dc=acegisecurity,dc=org</tt>. * * @return the root DN calculated from the path of the LDAP url. */ public String getRootDn() { return rootDn; } /** * Connects anonymously unless a manager user has been specified, in which case it will bind as the * manager. * * @return the resulting context object. */ public DirContext newInitialDirContext() { if (managerDn != null) { return newInitialDirContext(managerDn, managerPassword); } Hashtable env = getEnvironment(); env.put(Context.SECURITY_AUTHENTICATION, AUTH_TYPE_NONE); return connect(env); } public DirContext newInitialDirContext(String username, String password) { Hashtable env = getEnvironment(); // Don't pool connections for individual users if (!username.equals(managerDn)) { env.remove(CONNECTION_POOL_KEY); } env.put(Context.SECURITY_PRINCIPAL, username); env.put(Context.SECURITY_CREDENTIALS, password); return connect(env); } public void setAuthenticationType(String authenticationType) { Assert.hasLength(authenticationType, "LDAP Authentication type must not be empty or null"); this.authenticationType = authenticationType; } /** * Sets any custom environment variables which will be added to the those returned * by the <tt>getEnvironment</tt> method. * * @param extraEnvVars extra environment variables to be added at config time. */ public void setExtraEnvVars(Map extraEnvVars) { Assert.notNull(extraEnvVars, "Extra environment map cannot be null."); this.extraEnvVars = extraEnvVars; } public void setInitialContextFactory(String initialContextFactory) { Assert.hasLength(initialContextFactory, "Initial context factory name cannot be empty or null"); this.initialContextFactory = initialContextFactory; } /** * Sets the directory user to authenticate as when obtaining a context using the * <tt>newInitialDirContext()</tt> method. * If no name is supplied then the context will be obtained anonymously. * * @param managerDn The name of the "manager" user for default authentication. */ public void setManagerDn(String managerDn) { Assert.hasLength(managerDn, "Manager user name cannot be empty or null."); this.managerDn = managerDn; } /** * Sets the password which will be used in combination with the manager DN. * * @param managerPassword The "manager" user's password. */ public void setManagerPassword(String managerPassword) { Assert.hasLength(managerPassword, "Manager password must not be empty or null."); this.managerPassword = managerPassword; } public void setMessageSource(MessageSource messageSource) { this.messages = new MessageSourceAccessor(messageSource); } /** * Connection pooling is enabled by default for anonymous or "manager" connections when using the default * Sun provider. To disable all connection pooling, set this property to false. * * @param useConnectionPool whether to pool connections for non-specific users. */ public void setUseConnectionPool(boolean useConnectionPool) { this.useConnectionPool = useConnectionPool; } public void setUseLdapContext(boolean useLdapContext) { this.useLdapContext = useLdapContext; } }