/* * Copyright 2002-2016 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.security.ldap; import java.util.ArrayList; import java.util.Hashtable; import java.util.List; import java.util.StringTokenizer; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.ldap.core.support.DirContextAuthenticationStrategy; import org.springframework.ldap.core.support.LdapContextSource; import org.springframework.ldap.core.support.SimpleDirContextAuthenticationStrategy; import org.springframework.util.Assert; /** * ContextSource implementation which uses Spring LDAP's <tt>LdapContextSource</tt> as a * base class. Used internally by the Spring Security LDAP namespace configuration. * <p> * From Spring Security 3.0, Spring LDAP 1.3 is used and the <tt>ContextSource</tt> * interface provides support for binding with a username and password. As a result, * Spring LDAP <tt>ContextSource</tt> implementations such as <tt>LdapContextSource</tt> * may be used directly with Spring Security. * <p> * Spring LDAP 1.3 doesn't have JVM-level LDAP connection pooling enabled by default. This * class sets the <tt>pooled</tt> property to true, but customizes the * {@link DirContextAuthenticationStrategy} used to disable pooling when the <tt>DN</tt> * doesn't match the <tt>userDn</tt> property. This prevents pooling for calls to * {@link #getContext(String, String)} to authenticate as specific users. * * @author Luke Taylor * @since 2.0 */ public class DefaultSpringSecurityContextSource extends LdapContextSource { protected final Log logger = LogFactory.getLog(getClass()); private String rootDn; /** * Create and initialize an instance which will connect to the supplied LDAP URL. If * you want to use more than one server for fail-over, rather use the * {@link #DefaultSpringSecurityContextSource(List, String)} constructor. * * @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<String> urls = new ArrayList<String>(); // 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))); this.logger.info(" URL '" + url + "', root DN is '" + urlRootDn + "'"); if (this.rootDn == null) { this.rootDn = urlRootDn; } else if (!this.rootDn.equals(urlRootDn)) { throw new IllegalArgumentException( "Root DNs must be the same when using multiple URLs"); } } setUrls(urls.toArray(new String[urls.size()])); setBase(this.rootDn); setPooled(true); setAuthenticationStrategy(new SimpleDirContextAuthenticationStrategy() { @Override @SuppressWarnings("rawtypes") public void setupEnvironment(Hashtable env, String dn, String password) { super.setupEnvironment(env, dn, password); // Remove the pooling flag unless we are authenticating as the 'manager' // user. if (!DefaultSpringSecurityContextSource.this.userDn.equals(dn) && env.containsKey(SUN_LDAP_POOLING_FLAG)) { DefaultSpringSecurityContextSource.this.logger .debug("Removing pooling flag for user " + dn); env.remove(SUN_LDAP_POOLING_FLAG); } } }); } /** * Create and initialize an instance which will connect of the LDAP Spring Security * Context Source. It will connect to any of the provided LDAP server URLs. * * @param urls A list of string values which are LDAP server URLs. An example would be * <code>ldap://ldap.company.com:389</code>. LDAPS URLs (SSL-secured) may be used as * well, given that Spring Security is able to connect to the server. Note that these * <b>URLs must not include the base DN</b>! * @param baseDn The common Base DN for all provided servers, e.g. * * <pre> * dc=company,dc=com * </pre> * * . */ public DefaultSpringSecurityContextSource(List<String> urls, String baseDn) { this(buildProviderUrl(urls, baseDn)); } /** * Builds a Spring LDAP-compliant Provider URL string, i.e. a space-separated list of * LDAP servers with their base DNs. As the base DN must be identical for all servers, * it needs to be supplied only once. * * @param urls A list of string values which are LDAP server URLs. An example would be * * <pre> * ldap://ldap.company.com:389 * </pre> * * . LDAPS URLs may be used as well, given that Spring Security is able to connect to * the server. * @param baseDn The common Base DN for all provided servers, e.g. * * <pre> * dc=company,dc=com * </pre> * * . * @return A Spring Security/Spring LDAP-compliant Provider URL string. */ private static String buildProviderUrl(List<String> urls, String baseDn) { Assert.notNull(baseDn, "The Base DN for the LDAP server must not be null."); Assert.notEmpty(urls, "At least one LDAP server URL must be provided."); String trimmedBaseDn = baseDn.trim(); StringBuilder providerUrl = new StringBuilder(); for (String serverUrl : urls) { String trimmedUrl = serverUrl.trim(); if ("".equals(trimmedUrl)) { continue; } providerUrl.append(trimmedUrl); if (!trimmedUrl.endsWith("/")) { providerUrl.append("/"); } providerUrl.append(trimmedBaseDn); providerUrl.append(" "); } return providerUrl.toString(); } }