Skip to content

Instantly share code, notes, and snippets.

@stephen-masters
Created January 16, 2013 09:47
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save stephen-masters/4545962 to your computer and use it in GitHub Desktop.
Save stephen-masters/4545962 to your computer and use it in GitHub Desktop.
This is a read-only Seam identity store implementation, which I knocked up to enable Drools Guvnor to authenticate users against Microsoft Active Directory. To use it, produce a .jar containing this class and put it in your Guvnor web application lib directory. I would recommend creating a .jar which only contains this class, as it is best to mi…
package uk.co.scattercode.security.seam;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attributes;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.InitialLdapContext;
import javax.naming.ldap.LdapContext;
import org.jboss.seam.security.Credentials;
import org.jboss.seam.security.Identity;
import org.jboss.seam.security.management.LdapIdentityStore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This is a read-only Seam identity store implementation to authenticate users
* against Microsoft Active Directory.
* <p>
* To make use of this authenticator, you first need to build this project. Put
* the resulting .jar into the WEB-INF/lib directory of your web application.
* You will then need to put something similar to the following into the Seam
* components.xml file (in WEB-INF) and restart the web server.
* </p>
*
* <pre>
* &lt;component name="activeDirectoryAuthenticator"
* class="uk.co.scattercode.security.seam.ActiveDirectoryIdentityStore"
* startup="true" scope="APPLICATION"&gt;
* &lt;property name="domain"&gt;mydomain.local&lt;/property&gt;
* &lt;property name="serverAddress"&gt;myadhost.local&lt;/property&gt;
* &lt;property name="serverPort"&gt;389&lt;/property&gt;
* &lt;property name="sysUser"&gt;readonlyuser&lt;/property&gt;
* &lt;property name="sysPassword"&gt;r3@donlyus3rpwd&lt;/property&gt;
* &lt;property name="userContextDn"&gt;OU=My Company,DC=mydomain,DC=local&lt;/property&gt;
* &lt;/component&gt;
*
* &lt;security:identity authenticate-method="#{activeDirectoryAuthenticator.authenticate}" /&gt;
* </pre>
* <p>
* The "sysUser" must have read access to Active Directory. If it is created as
* an A/D user, then this should be the case. Write access is not necessary, as
* this implementation is deliberately restricted to authentication only.
* </p>
* <p>
* As a note of warning, this authenticator will lower-case the username before
* adding it to the security context. This is a workaround for those situations
* where Active Directory does not enforce case-sensitive user names, but the
* web application (Guvnor is one example) does.
* </p>
*
* @author Stephen Masters
*/
public class ActiveDirectoryIdentityStore extends LdapIdentityStore {
/** Generated serialVersionUID. */
private static final long serialVersionUID = -3999965613116614977L;
private static Logger log = LoggerFactory.getLogger(ActiveDirectoryIdentityStore.class);
private String domain;
private String serverAddress;
private int serverPort;
private String sysuser;
private String syspassword;
private String userContextDn;
/**
* This no-args authenticate() method is required for Seam JAAS authentication.
*/
public boolean authenticate() {
Credentials credentials = Identity.instance().getCredentials();
if (credentials.getUsername() == null || credentials.getPassword() == null) {
log.error("Login attempted with null credentials.");
return false;
}
// Active Directory is not case-sensitive when searching for users.
// However, Drools Guvnor users for example, are case-sensitive.
// This can lead to an issue where a user is able to login with a
// mixture of upper and lower case for their username, but when this
// username is added to the context, it fails to map to the application
// username. To work around this issue, this authenticator will
// lower-case the username when adding it to the context. This means
// that as long as you create users in Guvnor with lower case names,
// they should map successfully.
credentials.setUsername(credentials.getUsername().toLowerCase());
return authenticate(credentials.getUsername(), credentials.getPassword());
}
@Override
public boolean authenticate(String username, String password) {
try {
// If an LDAP context can be created with the user name and password,
// the user credentials are valid.
@SuppressWarnings("unused")
LdapContext ctxGC = getContext(username, password);
} catch (NamingException e) {
log.error("Login attempted with invalid credentials by user: " + username);
return false;
}
Attributes userAttributes = findUser(username);
if (userAttributes != null) {
return true;
} else {
return false;
}
}
/**
* Performs an active directory search in the context of the system user.
*/
private Attributes findUser(String username) {
try {
LdapContext ctxGC = getContext();
log.info("Connected to LDAP context.");
String searchFilter = "(&(objectClass=user)(sAMAccountName=" + username + "))";
SearchControls searchCtls = new SearchControls();
// Search through all sub-directories to find the user.
searchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE);
// Define which attributes we wish to get hold of.
String[] returnedAtts = { "sn", "givenName", "mail", "distinguishedName", "memberOf" };
searchCtls.setReturningAttributes(returnedAtts);
log.info("Searching for: " + searchFilter);
NamingEnumeration<SearchResult> results = ctxGC.search(this.userContextDn, searchFilter, searchCtls);
while (results.hasMoreElements()) {
SearchResult result = results.next();
log.info("Results: " + result.toString());
Attributes attrs = result.getAttributes();
if (attrs != null) {
return attrs;
}
}
} catch (NamingException e) {
log.error("NamingException finding user.", e);
}
// Didn't find anything.
return null;
}
private LdapContext getContext(String username, String password) throws NamingException {
log.info("Connecting to LDAP context...");
Hashtable<String, String> env = new Hashtable<String, String>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://" + this.serverAddress + ":" + this.serverPort);
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, username + "@" + this.domain);
env.put(Context.SECURITY_CREDENTIALS, password);
LdapContext ctx = new InitialLdapContext(env, null);
return ctx;
}
private LdapContext getContext() throws NamingException {
log.info("Connecting to LDAP context...");
Hashtable<String, String> env = new Hashtable<String, String>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://" + this.serverAddress + ":" + this.serverPort);
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, this.sysuser + "@" + this.domain);
env.put(Context.SECURITY_CREDENTIALS, this.syspassword);
return new InitialLdapContext(env, null);
}
public boolean isUserEnabled(String username) {
Attributes userAttributes = findUser(username);
if (userAttributes != null) {
if ("TRUE".equals(userAttributes.get("msRTCSIP-UserEnabled"))) {
return true;
}
}
return false;
}
public List<String> getGrantedRoles(String username) {
Attributes userAttributes = findUser(username);
String memberships = userAttributes.get("memberOf").toString();
Set<String> roles = new TreeSet<String>();
if (memberships != null) {
for (String attr : memberships.split(",")) {
if (attr.contains("CN=")) {
// Role is the value to the right of the equals symbol.
roles.add(attr.split("=")[1]);
}
}
}
return new ArrayList<String>(roles);
}
/**
* This is a read-only LDAP identity store, so this method will return false
* for all of the optional 'mutating' features.
*/
public boolean supportsFeature(Feature feature) {
return false;
}
/**
* The LDAP host server.
*/
@Override
public String getServerAddress() {
return this.serverAddress;
}
public void setServerAddress(String host) {
this.serverAddress = host;
}
/**
* The LDAP port on the host server.
*/
@Override
public int getServerPort() {
return this.serverPort;
}
public void setServerPort(int port) {
this.serverPort = port;
}
/**
* The system user name to be used by the application to connect to Active Directory.
*/
public String getSysUser() {
return sysuser;
}
public void setSysUser(String sysuser) {
this.sysuser = sysuser;
}
/**
* The system user password to connect to Active Directory.
*/
public String getSysPassword() {
return syspassword;
}
public void setSysPassword(String syspassword) {
this.syspassword = syspassword;
}
/**
* The Active Directory domain.
*/
public String getDomain() {
return domain;
}
public void setDomain(String domain) {
this.domain = domain;
}
/**
* The LDAP Distinguished Name where users can be found.
*/
public String getUserContextDn() {
return userContextDn;
}
public void setUserContextDn(String dn) {
this.userContextDn = dn;
}
}
@cinlloc
Copy link

cinlloc commented Jun 19, 2015

Thx! It helped me a lot to work with f... Active Directory in JBoss Seam!

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