Created
July 24, 2012 03:27
-
-
Save xaviershay/3167835 to your computer and use it in GitHub Desktop.
LDAP Authentication for dropwizard
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.