Skip to content

Instantly share code, notes, and snippets.

@xaviershay
Created July 24, 2012 03:27
Show Gist options
  • Star 13 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save xaviershay/3167835 to your computer and use it in GitHub Desktop.
Save xaviershay/3167835 to your computer and use it in GitHub Desktop.
LDAP Authentication for dropwizard
package com.squareup.alcatraz.auth;
import com.google.common.base.Optional;
import com.unboundid.ldap.sdk.Filter;
import com.unboundid.ldap.sdk.LDAPConnection;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.ResultCode;
import com.unboundid.ldap.sdk.SearchRequest;
import com.unboundid.ldap.sdk.SearchResult;
import com.unboundid.ldap.sdk.SearchResultEntry;
import com.unboundid.ldap.sdk.SearchScope;
import com.yammer.dropwizard.auth.AuthenticationException;
import com.yammer.dropwizard.auth.Authenticator;
import com.yammer.dropwizard.auth.basic.BasicCredentials;
import com.yammer.dropwizard.logging.Log;
import java.io.IOException;
import java.util.LinkedHashSet;
import java.util.Set;
import sun.security.x509.X500Name;
/**
* An authentication scheme that takes an existing connection to an LDAP server and uses it to first
* look up a user's DN, bind with that DN and the supplied password to verify the authentication,
* then performs a search for the user's roles using the initial connection.
*
* This is a reasonably complicated setup, but allows for a lot of flexibility in your LDAP
* configuration.
*/
public class LdapAuthenticator implements Authenticator<BasicCredentials, User> {
/**
* Creates a new authenticator
*
* @param connectionFactory
* @param searchDN the base DN in which to perform a search for the user's DN by uid.
* @param rolesDN the base DN in which to lookup roles for a user.
*/
public LdapAuthenticator(LdapConnectionFactory connectionFactory, String searchDN, String rolesDN) {
this.connectionFactory = connectionFactory;
this.searchDN = searchDN;
this.rolesDN = rolesDN;
}
@Override
public Optional<User> authenticate(BasicCredentials credentials) throws AuthenticationException {
try {
String username = sanitizeUsername(credentials.getUsername());
String userDN = dnFromUsername(username);
verifyCredentials(credentials, userDN);
Set<String> roles = rolesFromDN(userDN);
return Optional.fromNullable(new User());
} catch (LDAPException le) {
if (invalidCredentials(le)) {
throw new AuthenticationException("Could not connect to LDAP server", le);
} else {
return Optional.absent();
}
}
}
private boolean invalidCredentials(LDAPException le) {
return le.getResultCode() != ResultCode.INVALID_CREDENTIALS;
}
private void verifyCredentials(BasicCredentials credentials, String userDN) throws LDAPException {
LDAPConnection authenticatedConnection =
connectionFactory.getLDAPConnection(userDN, credentials.getPassword());
authenticatedConnection.close();
}
private String dnFromUsername(String username) throws LDAPException {
LDAPConnection connection = connectionFactory.getLDAPConnection();
try {
SearchRequest searchRequest =
new SearchRequest(searchDN, SearchScope.SUB, "(uid=" + username + ")");
SearchResult sr = connection.search(searchRequest);
if (sr.getEntryCount() == 0) {
throw new LDAPException(ResultCode.INVALID_CREDENTIALS);
}
return sr.getSearchEntries().get(0).getDN();
} finally {
connection.close();
}
}
private Set<String> rolesFromDN(String userDN) throws LDAPException {
LDAPConnection connection = connectionFactory.getLDAPConnection();
SearchRequest searchRequest = new SearchRequest(rolesDN,
SearchScope.SUB, Filter.createEqualityFilter("uniqueMember", userDN));
Set<String> applicationAccessSet = new LinkedHashSet<String>();
try {
SearchResult sr = connection.search(searchRequest);
for (SearchResultEntry sre : sr.getSearchEntries()) {
try {
X500Name myName = new X500Name(sre.getDN());
applicationAccessSet.add(myName.getCommonName());
} catch (IOException e) {
logger.error("Could not create X500 Name for role:" + sre.getDN(), e);
}
}
} finally {
connection.close();
}
return applicationAccessSet;
}
private String sanitizeUsername(String username) {
return username.replaceAll("[^A-Za-z0-9-_.]", "");
}
private static final Log logger = Log.forClass(LdapAuthenticator.class);
private LdapConnectionFactory connectionFactory;
private String searchDN;
private String rolesDN;
}
package com.squareup.alcatraz.auth;
import com.unboundid.ldap.sdk.LDAPConnection;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.util.ssl.SSLUtil;
import com.unboundid.util.ssl.TrustAllTrustManager;
import java.security.GeneralSecurityException;
import com.yammer.dropwizard.logging.Log;
/**
* Creates connections to an LDAP server given the a set of default credentials, but allows those
* credentials to be overridden on a per-connection basis.
*/
public class LdapConnectionFactory {
public LdapConnectionFactory(String server, int port, String userDN, String password) {
this.server = server;
this.port = port;
this.userDN = userDN;
this.password = password;
}
public LDAPConnection getLDAPConnection() throws LDAPException {
return getLDAPConnection(userDN, password);
}
public LDAPConnection getLDAPConnection(String userDN, String password) throws LDAPException {
// Use the following if your LDAP server doesn't have a valid certificate.
/*
SSLUtil sslUtil = new SSLUtil(new TrustAllTrustManager());
LDAPConnection ldapConnection = null;
try {
ldapConnection = new LDAPConnection(sslUtil.createSSLSocketFactory());
} catch (GeneralSecurityException gse) {
logger.error("Couldn't create SSL socket factory", gse);
}
*/
LDAPConnection ldapConnection = new LDAPConnection(SSLSocketFactory.getDefault());
ldapConnection.connect(server, port);
ldapConnection.bind(userDN, password);
return ldapConnection;
}
private static final Log logger = Log.forClass(LdapConnectionFactory.class);
private final String server;
private final int port;
private final String userDN;
private final String password;
}
@ariens
Copy link

ariens commented Jul 2, 2015

Hey Xavier--this was fantastic, thanks! Working with Active Directory here and had to modify:

new SearchRequest(searchDN, SearchScope.SUB, "(uid=" + username + ")");
to:
new SearchRequest(searchDN, SearchScope.SUB, "(sAMAccountName=" + username + ")");

I know nothing about AD/LDAP so I'm not sure if this was our implementation of it or a Microsoft thing, either way--thanks again.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment