org.springframework.security.ldap.DefaultSpringSecurityContextSource Maven / Gradle / Ivy
/*
* Copyright 2002-2021 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
*
* https://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.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
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.core.log.LogMessage;
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 LdapContextSource as a
* base class. Used internally by the Spring Security LDAP namespace configuration.
*
* From Spring Security 3.0, Spring LDAP 1.3 is used and the ContextSource
* interface provides support for binding with a username and password. As a result,
* Spring LDAP ContextSource implementations such as LdapContextSource
* may be used directly with Spring Security.
*
* Spring LDAP 1.3 doesn't have JVM-level LDAP connection pooling enabled by default. This
* class sets the pooled property to true, but customizes the
* {@link DirContextAuthenticationStrategy} used to disable pooling when the DN
* doesn't match the userDn 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());
/**
* 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
* ldap://localhost:389/base_dn
*/
public DefaultSpringSecurityContextSource(String providerUrl) {
Assert.hasLength(providerUrl, "An LDAP connection URL must be supplied.");
StringTokenizer tokenizer = new StringTokenizer(providerUrl);
ArrayList urls = new ArrayList<>();
// Work out rootDn from the first URL and check that the other URLs (if any) match
String rootDn = null;
while (tokenizer.hasMoreTokens()) {
String url = tokenizer.nextToken();
String urlRootDn = LdapUtils.parseRootDnFromUrl(url);
urls.add(url.substring(0, url.lastIndexOf(urlRootDn)));
this.logger.info(LogMessage.format("Configure with URL %s and root DN %s", url, urlRootDn));
Assert.isTrue(rootDn == null || rootDn.equals(urlRootDn),
"Root DNs must be the same when using multiple URLs");
rootDn = (rootDn != null) ? rootDn : urlRootDn;
}
setUrls(urls.toArray(new String[0]));
setBase((rootDn != null) ? decodeUrl(rootDn) : null);
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 authenticating as the 'manager' user.
if (!DefaultSpringSecurityContextSource.this.userDn.equals(dn)
&& env.containsKey(SUN_LDAP_POOLING_FLAG)) {
DefaultSpringSecurityContextSource.this.logger.trace("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
* ldap://ldap.company.com:389
. LDAPS URLs (SSL-secured) may be used as
* well, given that Spring Security is able to connect to the server. Note that these
* URLs must not include the base DN!
* @param baseDn The common Base DN for all provided servers, e.g.
*
*
* dc=company,dc=com
*
*
* .
*/
public DefaultSpringSecurityContextSource(List 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
*
*
* ldap://ldap.company.com:389
*
*
* . 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.
*
*
* dc=company,dc=com
*
*
* .
* @return A Spring Security/Spring LDAP-compliant Provider URL string.
*/
private static String buildProviderUrl(List 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 encodedBaseDn = encodeUrl(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(encodedBaseDn);
providerUrl.append(" ");
}
return providerUrl.toString();
}
private static String encodeUrl(String url) {
try {
return URLEncoder.encode(url, StandardCharsets.UTF_8.toString());
}
catch (UnsupportedEncodingException ex) {
throw new IllegalStateException(ex);
}
}
private String decodeUrl(String url) {
try {
return URLDecoder.decode(url, StandardCharsets.UTF_8.toString());
}
catch (UnsupportedEncodingException ex) {
throw new IllegalStateException(ex);
}
}
}