org.springframework.ldap.core.support.AbstractTlsDirContextAuthenticationStrategy Maven / Gradle / Ivy
/*
* Copyright 2005-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ldap.core.support;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Hashtable;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import javax.naming.ldap.LdapContext;
import javax.naming.ldap.StartTlsRequest;
import javax.naming.ldap.StartTlsResponse;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSocketFactory;
import org.springframework.ldap.UncategorizedLdapException;
import org.springframework.ldap.core.DirContextProxy;
import org.springframework.ldap.support.LdapUtils;
/**
* Abstract superclass for {@link DirContextAuthenticationStrategy}
* implementations that apply TLS security to the connections. The supported TLS
* behavior differs between servers. E.g., some servers expect the TLS
* connection be shut down gracefully before the actual target context is
* closed, whereas other servers do not support that. The
* shutdownTlsGracefully
property controls this behavior; the
* property defaults to false
.
*
* The SSLSocketFactory
used for TLS negotiation can be customized
* using the sslSocketFactory
property. This allows for example a
* socket factory that can load the keystore/truststore using the Spring
* Resource abstraction. This provides a much more Spring-like strategy for
* configuring PKI credentials for authentication, in addition to allowing
* application-specific keystores and truststores running in the same JVM.
*
* In some rare occasions there is a need to supply a
* HostnameVerifier
to the TLS processing instructions in order to
* have the returned certificate properly validated. If a
* HostnameVerifier
is supplied to
* {@link #setHostnameVerifier(HostnameVerifier)}, that will be applied to the
* processing.
*
*
* For further information regarding TLS, refer to this
* page.
*
* NB: TLS negotiation is an expensive process, which is why you will
* most likely want to use connection pooling, to make sure new connections are
* not created for each individual request. It is imperative however, that the
* built-in LDAP connection pooling is not used in combination with the TLS
* AuthenticationStrategy implementations - this will not work. You should use
* the Spring LDAP PoolingContextSource instead.
*
* @author Mattias Hellborg Arthursson
*/
public abstract class AbstractTlsDirContextAuthenticationStrategy implements DirContextAuthenticationStrategy {
/** Hostname verifier to use for cert subject validation */
private HostnameVerifier hostnameVerifier;
/** Flag to cause graceful shutdown required by some LDAP DSAs */
private boolean shutdownTlsGracefully = false;
/** SSL socket factory to use for startTLS negotiation */
private SSLSocketFactory sslSocketFactory;
/**
* Specify whether the TLS should be shut down gracefully before the target
* context is closed. Defaults to false
.
*
* @param shutdownTlsGracefully true
to shut down the TLS
* connection explicitly, false
closes the target context
* immediately.
*/
public void setShutdownTlsGracefully(boolean shutdownTlsGracefully) {
this.shutdownTlsGracefully = shutdownTlsGracefully;
}
/**
* Set the optional
* HostnameVerifier to use for verifying incoming certificates. Defaults to null
* , meaning that the default hostname verification will take place.
*
* @param hostnameVerifier The HostnameVerifier
to use, if any.
*/
public void setHostnameVerifier(HostnameVerifier hostnameVerifier) {
this.hostnameVerifier = hostnameVerifier;
}
/**
* Sets the optional SSL socket factory used for startTLS negotiation.
* Defaults to null
to indicate that the default socket factory
* provided by the underlying JSSE provider should be used.
* @param sslSocketFactory SSL socket factory to use, if any.
*/
public void setSslSocketFactory(final SSLSocketFactory sslSocketFactory) {
this.sslSocketFactory = sslSocketFactory;
}
/* (non-Javadoc)
* @see org.springframework.ldap.core.support.DirContextAuthenticationStrategy#setupEnvironment(java.util.Hashtable, java.lang.String, java.lang.String)
*/
public final void setupEnvironment(Hashtable env, String userDn, String password) {
// Nothing to do in this implementation - authentication should take
// place after TLS has been negotiated.
}
/* (non-Javadoc)
* @see org.springframework.ldap.core.support.DirContextAuthenticationStrategy#processContextAfterCreation(javax.naming.directory.DirContext, java.lang.String, java.lang.String)
*/
public final DirContext processContextAfterCreation(DirContext ctx, String userDn, String password)
throws NamingException {
if (ctx instanceof LdapContext) {
final LdapContext ldapCtx = (LdapContext) ctx;
final StartTlsResponse tlsResponse = (StartTlsResponse) ldapCtx.extendedOperation(new StartTlsRequest());
try {
if (hostnameVerifier != null) {
tlsResponse.setHostnameVerifier(hostnameVerifier);
}
tlsResponse.negotiate(sslSocketFactory); // If null, the default SSL socket factory is used
applyAuthentication(ldapCtx, userDn, password);
if (shutdownTlsGracefully) {
// Wrap the target context in a proxy to intercept any calls
// to 'close', so that we can shut down the TLS connection
// gracefully first.
return (DirContext) Proxy.newProxyInstance(DirContextProxy.class.getClassLoader(), new Class[] {
LdapContext.class, DirContextProxy.class }, new TlsAwareDirContextProxy(ldapCtx,
tlsResponse));
}
else {
return ctx;
}
}
catch (IOException e) {
LdapUtils.closeContext(ctx);
throw new UncategorizedLdapException("Failed to negotiate TLS session", e);
}
}
else {
throw new IllegalArgumentException(
"Processed Context must be an LDAPv3 context, i.e. an LdapContext implementation");
}
}
/**
* Apply the actual authentication to the specified LdapContext
* . Typically, this will involve adding stuff to the environment.
*
* @param ctx the LdapContext
instance.
* @param userDn the user dn of the user to authenticate.
* @param password the password of the user to authenticate.
* @throws NamingException if any error occurs.
*/
protected abstract void applyAuthentication(LdapContext ctx, String userDn, String password) throws NamingException;
private static final class TlsAwareDirContextProxy implements DirContextProxy, InvocationHandler {
private static final String GET_TARGET_CONTEXT_METHOD_NAME = "getTargetContext";
private static final String CLOSE_METHOD_NAME = "close";
private final LdapContext target;
private final StartTlsResponse tlsResponse;
public TlsAwareDirContextProxy(LdapContext target, StartTlsResponse tlsResponse) {
this.target = target;
this.tlsResponse = tlsResponse;
}
public DirContext getTargetContext() {
return target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals(CLOSE_METHOD_NAME)) {
tlsResponse.close();
return method.invoke(target, args);
}
else if (method.getName().equals(GET_TARGET_CONTEXT_METHOD_NAME)) {
return target;
}
else {
return method.invoke(target, args);
}
}
}
}