-
1. Re: Role-mapping with LdapLoginModule and ActiveDirectory
zparticle Aug 27, 2004 9:12 AM (in response to logi)I've recently written a new login module for use with ActiveDirectory. Our schema didn't work with the LdapLoginModule at all. Perhaps this will help you. Our directory is set up a bit odd so I had write something new. Our AD is split into locations, for instance Building #1, Building #2. There is no single place in our AD to find users. We want the Windows network login and password to be used to log into the applications but the users are entered into AD with their full names. The network id is an attribute.
The code below handles the authentication and authorization of the users in AD. see the comments for an example of the login-config and how to use the various parameters./* * JBoss, the OpenSource WebOS * * Distributable under LGPL license. * See terms of license at gnu.org. */ //package org.jboss.security.auth.spi; import java.security.acl.Group; import java.security.Principal; import java.util.Iterator; import java.util.Map.Entry; import java.util.Properties; import java.util.Set; import java.util.StringTokenizer; import javax.naming.Context; import javax.naming.NamingEnumeration; import javax.naming.NamingException; import javax.naming.directory.Attribute; import javax.naming.directory.Attributes; import javax.naming.directory.BasicAttributes; import javax.naming.directory.SearchResult; import javax.naming.ldap.InitialLdapContext; import javax.security.auth.login.LoginException; import org.jboss.security.SimpleGroup; import org.jboss.security.SimplePrincipal; import org.jboss.security.auth.spi.UsernamePasswordLoginModule; /** * An implementation of LoginModule that authenticates against an LDAP server * using JNDI, based on the configuration properties. * <p> * The LoginModule options include whatever options your LDAP JNDI provider * supports. Examples of standard property names are: * <ul> * <li><code>Context.INITIAL_CONTEXT_FACTORY = "java.naming.factory.initial"</code> * <li><code>Context.SECURITY_PROTOCOL = "java.naming.security.protocol"</code> * <li><code>Context.PROVIDER_URL = "java.naming.provider.url"</code> * <li><code>Context.SECURITY_AUTHENTICATION = "java.naming.security.authentication"</code> * </ul> * <p> * The Context.SECURITY_PRINCIPAL is set to the distinguished name of the user * as obtained by the callback handler. * <p> * Additional module properties include: * <ul> * <li>principalSerachStartDNs : A ; delimited list of DNs to search under to find the * user. * <p> * OU=Users,OU=IT,OU=Departments,OU=Building 1,DC=company,DC=com;OU=Users,OU=Finance,OU=Departments,OU=Building 1,DC=company,DC=com; * <p> * an opaque Object using the <code>org.jboss.security.plugins.ObjectCallback</code> type * of Callback rather than as a char[] password using a JAAS PasswordCallback. * <li>principalIDAttributeName : The name of the attribute that is the id to match an object as the user * that is logging in. If the user logs in with the network id then sAMAccountName should be used here. * <li>principalSearchObjectClass : Used to narrow the list of matching objects to a specific type during the * search. Uses the objectClass attribute. This is typically set to user. * <li>principalRoleAttributeName : The name of the attribute that contains the user roles * <li>roleNameAttributeName : The name of the attribute in the role CN that is the name of the role for the user * <li>performAuthentication : Defaults to true, if false the users password is not verified. The users roles are set up. * * This class was based on the LdapLoginModule class by Scott.Stark@jboss.org. * * <p>Example login-config.xml<p> <pre> <application-policy name = "ldap"> <authentication> <login-module code = "ActiveDirectoryLoginModule" flag = "required" > <module-option name="java.naming.factory.initial">com.sun.jndi.ldap.LdapCtxFactory</module-option> <module-option name="java.naming.provider.url">ldap://127.0.0.1:389</module-option> <module-option name="java.naming.security.authentication">simple</module-option> <module-option name="java.naming.security.principal">CN=ldap,OU=Service Accounts,DC=company,DC=com</module-option> <module-option name="java.naming.security.credentials">password</module-option> <module-option name="principalSearchStartDNs"> OU=Users,OU=IT,OU=Departments,OU=Interlocken 380,DC=mcdata,DC=com </module-option> <module-option name="principalIDAttrbuteName">sAMAccountName</module-option> <module-option name="principalSearchObjectClass">user</module-option> <module-option name="principalRoleAttributeName">memberOf</module-option> <module-option name="roleNameAttributeName">cn</module-option> </login-module> </authentication> </application-policy> </pre> * * @author Scott.Shaver@mcdata.com */ public class ActiveDirectoryLoginModule extends UsernamePasswordLoginModule { private static final String PRINCIPAL_SEARCH_DNS = "principalSearchStartDNs"; private static final String PRINCIPAL_ID_ATTRIBUTE_NAME = "principalIDAttributeName"; private static final String PRINCIPAL_SEARCH_OBJECT_CLASS = "principalSearchObjectClass"; private static final String PRINCIPAL_ROLE_ATTRIBUTE_NAME = "principalRoleAttributeName"; private static final String ROLE_NAME_ATTRIBUTE_NAME = "roleNameAttributeName"; private static final String PERFORM_AUTHENTICATION = "performAuthentication"; public ActiveDirectoryLoginModule() { } private transient SimpleGroup userRoles = new SimpleGroup("Roles"); /** Overriden to return an empty password string as typically one cannot obtain a user's password. We also override the validatePassword so this is ok. @return and empty password String */ protected String getUsersPassword() throws LoginException { return ""; } /** Overriden by subclasses to return the Groups that correspond to the to the role sets assigned to the user. Subclasses should create at least a Group named "Roles" that contains the roles assigned to the user. A second common group is "CallerPrincipal" that provides the application identity of the user rather than the security domain identity. @return Group[] containing the sets of roles */ protected Group[] getRoleSets() throws LoginException { Group[] roleSets = {userRoles}; return roleSets; } /** Validate the inputPassword by creating a ldap InitialContext with the SECURITY_CREDENTIALS set to the password. @param inputPassword the password to validate. @param expectedPassword ignored */ protected boolean validatePassword(String inputPassword, String expectedPassword) { boolean isValid = false; String ldapAccount = (String)options.get("java.naming.security.principal"); Object ldapAccountPW = options.get("java.naming.security.credentials"); String performAuthentication = (String) options.get(PERFORM_AUTHENTICATION); if(performAuthentication == null) performAuthentication="true"; boolean doAuth = "true".equals(performAuthentication.toLowerCase()); if (doAuth && inputPassword != null) { log.debug("Performing authentication."); // See if this is an empty password that should be disallowed if (inputPassword.length() == 0) { super.log.debug("Rejecting empty password"); return false; } try { // first log in as the account to use to search with and find the user DN InitialLdapContext ctx = createLdapInitContext(ldapAccount, ldapAccountPW); if(ctx!=null) // we logged in now go find the user { String username = getUsername(); String userDN = findUser(ctx,username); ctx.close(); if(userDN!=null) { // Validate the password by trying to create an initial context InitialLdapContext uctx = createLdapInitContext(userDN, inputPassword); setupUserRoles(uctx,userDN); uctx.close(); isValid = true; } } } catch (NamingException e) { super.log.debug("Failed to validate password", e); } } else if(!doAuth) // don't validate the password just set up the roles { log.debug("Skipping authentication."); isValid = true; try { // first log in as the account to use to search with and find the user DN InitialLdapContext ctx = createLdapInitContext(ldapAccount, ldapAccountPW); if(ctx!=null) // we logged in now go find the user { String username = getUsername(); String userDN = findUser(ctx,username); if(userDN!=null) setupUserRoles(ctx,userDN); ctx.close(); } } catch (NamingException e) { super.log.debug("Failed to validate password", e); } } return isValid; } /** * @param ctx The ldap context * @param userDN String containing the full DN for the user */ private void setupUserRoles(InitialLdapContext ctx, String userDN) throws NamingException { log.debug("Performing authorization."); String principalRoleAttributeName = (String) options.get(PRINCIPAL_ROLE_ATTRIBUTE_NAME); if(principalRoleAttributeName == null) principalRoleAttributeName="memberOf"; String roleNameAttributeName = (String) options.get(ROLE_NAME_ATTRIBUTE_NAME); if(roleNameAttributeName == null) roleNameAttributeName="cn"; // we want only the attribute containing the roles returned String[] getattrs = {principalRoleAttributeName}; // get the attributes for the user Attributes attrs = ctx.getAttributes(userDN,getattrs); Attribute attr = attrs.get(principalRoleAttributeName); NamingEnumeration roles = attr.getAll(); while (roles.hasMore()) { String roleName = (String)roles.next(); // we want only the attribute containing the role name returned String[] rgetattrs = {roleNameAttributeName}; // get the attributes for the role Attributes rattrs = ctx.getAttributes(roleName,rgetattrs); Attribute rattr = rattrs.get(roleNameAttributeName); try { String shortRoleName = (String)rattr.get(); Principal p = super.createIdentity(shortRoleName); log.debug("Assign user to role " + shortRoleName + " from role DN " + roleName); userRoles.addMember(p); } catch(Exception x) { x.printStackTrace(); } } } /** * @param ctx The ldap context * @param username The name the user used to log in * @return String containing the full DN for the user */ private String findUser(InitialLdapContext ctx, String username) throws NamingException { String userDN = null; // get all of the starting search points String principalSearchStartDNs = (String) options.get(PRINCIPAL_SEARCH_DNS); if (principalSearchStartDNs == null) principalSearchStartDNs = ""; StringTokenizer st = new StringTokenizer(principalSearchStartDNs,";"); String[] searchHere = new String[st.countTokens()]; int index=0; while(st.hasMoreTokens()) searchHere[index++]=st.nextToken(); String principalIDAttributeName = (String) options.get(PRINCIPAL_ID_ATTRIBUTE_NAME); if (principalIDAttributeName == null) principalIDAttributeName = "sAMAccountName"; String principalSearchObjectClass = (String) options.get(PRINCIPAL_SEARCH_OBJECT_CLASS); if(principalSearchObjectClass == null) principalSearchObjectClass="user"; // set up the attrs to make a match on BasicAttributes matchAttrs = new BasicAttributes(true); matchAttrs.put(principalIDAttributeName, username); matchAttrs.put("objectClass", principalSearchObjectClass); for(int searchAt=0;searchAt<searchHere.length && userDN==null;searchAt++) { super.log.debug("Searching At: "+searchHere[searchAt]); NamingEnumeration answer = ctx.search(searchHere[searchAt], matchAttrs); while (answer.hasMore() && userDN==null) { super.log.debug("Found a result"); SearchResult sr = (SearchResult) answer.next(); Attributes attrs = sr.getAttributes(); Attribute dn = attrs.get("distinguishedName"); userDN = (String)dn.get(); } } super.log.debug("Returning userDN: "+userDN); return userDN; } private InitialLdapContext createLdapInitContext(String username, Object credential) throws NamingException { Properties env = new Properties(); // Map all option into the JNDI InitialLdapContext env Iterator iter = options.entrySet().iterator(); while (iter.hasNext()) { Entry entry = (Entry) iter.next(); env.put(entry.getKey(), entry.getValue()); } // Set defaults for key values if they are missing String factoryName = env.getProperty(Context.INITIAL_CONTEXT_FACTORY); if (factoryName == null) { factoryName = "com.sun.jndi.ldap.LdapCtxFactory"; env.setProperty(Context.INITIAL_CONTEXT_FACTORY, factoryName); } String authType = env.getProperty(Context.SECURITY_AUTHENTICATION); if (authType == null) env.setProperty(Context.SECURITY_AUTHENTICATION, "simple"); String protocol = env.getProperty(Context.SECURITY_PROTOCOL); String providerURL = (String) options.get(Context.PROVIDER_URL); if (providerURL == null) providerURL = "ldap://localhost:" + ((protocol != null && protocol.equals("ssl")) ? "636" : "389"); env.setProperty(Context.PROVIDER_URL, providerURL); env.setProperty(Context.SECURITY_PRINCIPAL, username); env.put(Context.SECURITY_CREDENTIALS, credential); super.log.debug("Logging into LDAP server, env=" + env); InitialLdapContext ctx = new InitialLdapContext(env, null); super.log.debug("Logged into LDAP server, " + ctx); return ctx; } }
-
2. Re: Role-mapping with LdapLoginModule and ActiveDirectory
starksm64 Aug 28, 2004 12:28 AM (in response to logi)Try reading the LdapLoginModule section in the free docs in chapter 8. Unless your roles are stored in a schema format that is compatible with the LdapLoginModule expectation, it won't be able to retrieve the roles.
-
3. Re: Role-mapping with LdapLoginModule and ActiveDirectory
zparticle Aug 30, 2004 11:22 AM (in response to logi)I've recently written a new login module for use with ActiveDirectory. Our schema didn't work with the LdapLoginModule at all. Perhaps this will help you. Our directory is set up a bit odd so I had write something new. Our AD is split into locations, for instance Building #1, Building #2. There is no single place in our AD to find users. We want the Windows network login and password to be used to log into the applications but the users are entered into AD with their full names. The network id is an attribute.
The code below handles the authentication and authorization of the users in AD. see the comments for an example of the login-config and how to use the various parameters./* * JBoss, the OpenSource WebOS * * Distributable under LGPL license. * See terms of license at gnu.org. */ //package org.jboss.security.auth.spi; import java.security.acl.Group; import java.security.Principal; import java.util.Iterator; import java.util.Map.Entry; import java.util.Properties; import java.util.Set; import java.util.StringTokenizer; import javax.naming.Context; import javax.naming.NamingEnumeration; import javax.naming.NamingException; import javax.naming.directory.Attribute; import javax.naming.directory.Attributes; import javax.naming.directory.BasicAttributes; import javax.naming.directory.SearchResult; import javax.naming.ldap.InitialLdapContext; import javax.security.auth.login.LoginException; import org.jboss.security.SimpleGroup; import org.jboss.security.SimplePrincipal; import org.jboss.security.auth.spi.UsernamePasswordLoginModule; /** * An implementation of LoginModule that authenticates against an LDAP server * using JNDI, based on the configuration properties. * <p> * The LoginModule options include whatever options your LDAP JNDI provider * supports. Examples of standard property names are: * <ul> * <li><code>Context.INITIAL_CONTEXT_FACTORY = "java.naming.factory.initial"</code> * <li><code>Context.SECURITY_PROTOCOL = "java.naming.security.protocol"</code> * <li><code>Context.PROVIDER_URL = "java.naming.provider.url"</code> * <li><code>Context.SECURITY_AUTHENTICATION = "java.naming.security.authentication"</code> * </ul> * <p> * The Context.SECURITY_PRINCIPAL is set to the distinguished name of the user * as obtained by the callback handler. * <p> * Additional module properties include: * <ul> * <li>principalSerachStartDNs : A ; delimited list of DNs to search under to find the * user. * <p> * OU=Users,OU=IT,OU=Departments,OU=Building 1,DC=company,DC=com;OU=Users,OU=Finance,OU=Departments,OU=Building 1,DC=company,DC=com; * <p> * an opaque Object using the <code>org.jboss.security.plugins.ObjectCallback</code> type * of Callback rather than as a char[] password using a JAAS PasswordCallback. * <li>principalIDAttributeName : The name of the attribute that is the id to match an object as the user * that is logging in. If the user logs in with the network id then sAMAccountName should be used here. * <li>principalSearchObjectClass : Used to narrow the list of matching objects to a specific type during the * search. Uses the objectClass attribute. This is typically set to user. * <li>principalRoleAttributeName : The name of the attribute that contains the user roles * <li>roleNameAttributeName : The name of the attribute in the role CN that is the name of the role for the user * <li>performAuthentication : Defaults to true, if false the users password is not verified. The users roles are set up. * * This class was based on the LdapLoginModule class by Scott.Stark@jboss.org. * * <p>Example login-config.xml<p> <pre> <application-policy name = "ldap"> <authentication> <login-module code = "ActiveDirectoryLoginModule" flag = "required" > <module-option name="java.naming.factory.initial">com.sun.jndi.ldap.LdapCtxFactory</module-option> <module-option name="java.naming.provider.url">ldap://127.0.0.1:389</module-option> <module-option name="java.naming.security.authentication">simple</module-option> <module-option name="java.naming.security.principal">CN=ldap,OU=Service Accounts,DC=company,DC=com</module-option> <module-option name="java.naming.security.credentials">password</module-option> <module-option name="principalSearchStartDNs"> OU=Users,OU=IT,OU=Departments,OU=Interlocken 380,DC=mcdata,DC=com </module-option> <module-option name="principalIDAttrbuteName">sAMAccountName</module-option> <module-option name="principalSearchObjectClass">user</module-option> <module-option name="principalRoleAttributeName">memberOf</module-option> <module-option name="roleNameAttributeName">cn</module-option> </login-module> </authentication> </application-policy> </pre> * * @author Scott.Shaver@mcdata.com */ public class ActiveDirectoryLoginModule extends UsernamePasswordLoginModule { private static final String PRINCIPAL_SEARCH_DNS = "principalSearchStartDNs"; private static final String PRINCIPAL_ID_ATTRIBUTE_NAME = "principalIDAttributeName"; private static final String PRINCIPAL_SEARCH_OBJECT_CLASS = "principalSearchObjectClass"; private static final String PRINCIPAL_ROLE_ATTRIBUTE_NAME = "principalRoleAttributeName"; private static final String ROLE_NAME_ATTRIBUTE_NAME = "roleNameAttributeName"; private static final String PERFORM_AUTHENTICATION = "performAuthentication"; public ActiveDirectoryLoginModule() { } private transient SimpleGroup userRoles = new SimpleGroup("Roles"); /** Overriden to return an empty password string as typically one cannot obtain a user's password. We also override the validatePassword so this is ok. @return and empty password String */ protected String getUsersPassword() throws LoginException { return ""; } /** Overriden by subclasses to return the Groups that correspond to the to the role sets assigned to the user. Subclasses should create at least a Group named "Roles" that contains the roles assigned to the user. A second common group is "CallerPrincipal" that provides the application identity of the user rather than the security domain identity. @return Group[] containing the sets of roles */ protected Group[] getRoleSets() throws LoginException { Group[] roleSets = {userRoles}; return roleSets; } /** Validate the inputPassword by creating a ldap InitialContext with the SECURITY_CREDENTIALS set to the password. @param inputPassword the password to validate. @param expectedPassword ignored */ protected boolean validatePassword(String inputPassword, String expectedPassword) { boolean isValid = false; String ldapAccount = (String)options.get("java.naming.security.principal"); Object ldapAccountPW = options.get("java.naming.security.credentials"); String performAuthentication = (String) options.get(PERFORM_AUTHENTICATION); if(performAuthentication == null) performAuthentication="true"; boolean doAuth = "true".equals(performAuthentication.toLowerCase()); if (doAuth && inputPassword != null) { log.debug("Performing authentication."); // See if this is an empty password that should be disallowed if (inputPassword.length() == 0) { super.log.debug("Rejecting empty password"); return false; } try { // first log in as the account to use to search with and find the user DN InitialLdapContext ctx = createLdapInitContext(ldapAccount, ldapAccountPW); if(ctx!=null) // we logged in now go find the user { String username = getUsername(); String userDN = findUser(ctx,username); ctx.close(); if(userDN!=null) { // Validate the password by trying to create an initial context InitialLdapContext uctx = createLdapInitContext(userDN, inputPassword); setupUserRoles(uctx,userDN); uctx.close(); isValid = true; } } } catch (NamingException e) { super.log.debug("Failed to validate password", e); } } else if(!doAuth) // don't validate the password just set up the roles { log.debug("Skipping authentication."); isValid = true; try { // first log in as the account to use to search with and find the user DN InitialLdapContext ctx = createLdapInitContext(ldapAccount, ldapAccountPW); if(ctx!=null) // we logged in now go find the user { String username = getUsername(); String userDN = findUser(ctx,username); if(userDN!=null) setupUserRoles(ctx,userDN); ctx.close(); } } catch (NamingException e) { super.log.debug("Failed to validate password", e); } } return isValid; } /** * @param ctx The ldap context * @param userDN String containing the full DN for the user */ private void setupUserRoles(InitialLdapContext ctx, String userDN) throws NamingException { log.debug("Performing authorization."); String principalRoleAttributeName = (String) options.get(PRINCIPAL_ROLE_ATTRIBUTE_NAME); if(principalRoleAttributeName == null) principalRoleAttributeName="memberOf"; String roleNameAttributeName = (String) options.get(ROLE_NAME_ATTRIBUTE_NAME); if(roleNameAttributeName == null) roleNameAttributeName="cn"; // we want only the attribute containing the roles returned String[] getattrs = {principalRoleAttributeName}; // get the attributes for the user Attributes attrs = ctx.getAttributes(userDN,getattrs); Attribute attr = attrs.get(principalRoleAttributeName); NamingEnumeration roles = attr.getAll(); while (roles.hasMore()) { String roleName = (String)roles.next(); // we want only the attribute containing the role name returned String[] rgetattrs = {roleNameAttributeName}; // get the attributes for the role Attributes rattrs = ctx.getAttributes(roleName,rgetattrs); Attribute rattr = rattrs.get(roleNameAttributeName); try { String shortRoleName = (String)rattr.get(); Principal p = super.createIdentity(shortRoleName); log.debug("Assign user to role " + shortRoleName + " from role DN " + roleName); userRoles.addMember(p); } catch(Exception x) { x.printStackTrace(); } } } /** * @param ctx The ldap context * @param username The name the user used to log in * @return String containing the full DN for the user */ private String findUser(InitialLdapContext ctx, String username) throws NamingException { String userDN = null; // get all of the starting search points String principalSearchStartDNs = (String) options.get(PRINCIPAL_SEARCH_DNS); if (principalSearchStartDNs == null) principalSearchStartDNs = ""; StringTokenizer st = new StringTokenizer(principalSearchStartDNs,";"); String[] searchHere = new String[st.countTokens()]; int index=0; while(st.hasMoreTokens()) searchHere[index++]=st.nextToken(); String principalIDAttributeName = (String) options.get(PRINCIPAL_ID_ATTRIBUTE_NAME); if (principalIDAttributeName == null) principalIDAttributeName = "sAMAccountName"; String principalSearchObjectClass = (String) options.get(PRINCIPAL_SEARCH_OBJECT_CLASS); if(principalSearchObjectClass == null) principalSearchObjectClass="user"; // set up the attrs to make a match on BasicAttributes matchAttrs = new BasicAttributes(true); matchAttrs.put(principalIDAttributeName, username); matchAttrs.put("objectClass", principalSearchObjectClass); for(int searchAt=0;searchAt<searchHere.length && userDN==null;searchAt++) { super.log.debug("Searching At: "+searchHere[searchAt]); NamingEnumeration answer = ctx.search(searchHere[searchAt], matchAttrs); while (answer.hasMore() && userDN==null) { super.log.debug("Found a result"); SearchResult sr = (SearchResult) answer.next(); Attributes attrs = sr.getAttributes(); Attribute dn = attrs.get("distinguishedName"); userDN = (String)dn.get(); } } super.log.debug("Returning userDN: "+userDN); return userDN; } private InitialLdapContext createLdapInitContext(String username, Object credential) throws NamingException { Properties env = new Properties(); // Map all option into the JNDI InitialLdapContext env Iterator iter = options.entrySet().iterator(); while (iter.hasNext()) { Entry entry = (Entry) iter.next(); env.put(entry.getKey(), entry.getValue()); } // Set defaults for key values if they are missing String factoryName = env.getProperty(Context.INITIAL_CONTEXT_FACTORY); if (factoryName == null) { factoryName = "com.sun.jndi.ldap.LdapCtxFactory"; env.setProperty(Context.INITIAL_CONTEXT_FACTORY, factoryName); } String authType = env.getProperty(Context.SECURITY_AUTHENTICATION); if (authType == null) env.setProperty(Context.SECURITY_AUTHENTICATION, "simple"); String protocol = env.getProperty(Context.SECURITY_PROTOCOL); String providerURL = (String) options.get(Context.PROVIDER_URL); if (providerURL == null) providerURL = "ldap://localhost:" + ((protocol != null && protocol.equals("ssl")) ? "636" : "389"); env.setProperty(Context.PROVIDER_URL, providerURL); env.setProperty(Context.SECURITY_PRINCIPAL, username); env.put(Context.SECURITY_CREDENTIALS, credential); super.log.debug("Logging into LDAP server, env=" + env); InitialLdapContext ctx = new InitialLdapContext(env, null); super.log.debug("Logged into LDAP server, " + ctx); return ctx; } }
-
4. Re: Role-mapping with LdapLoginModule and ActiveDirectory
jqp000 Jan 25, 2005 4:07 AM (in response to logi)Maybe LdapLoginModule.createInitialLdapContext() could be refactored e.g. by splitting role queries to a separate overridable method. This way you wouldn't have to cut-and-paste so much working code from ...auth.spi to adapt to the different flavors and extended schemas of AD or other LDAP providers...
This simple example helped me out a lot in querying user roles from AD. Quite simple after you have the right LDAP query. I did end up writing my own LoginModule, though...
http://forum.java.sun.com/thread.jspa?threadID=581444
//markku -
5. Re: Role-mapping with LdapLoginModule and ActiveDirectory
mbian Feb 15, 2005 1:18 PM (in response to logi)two problems in your module:
1. if a user doesn't belong any group, authentication will fail.
2. it only works if user is a immediate member of a group
e.g. user tom is a member of Uk_its, uk_its is a member of world_its, if access grant to world_its, tom doesn't have access.
here is fixed code:
private void setupUserRoles(InitialLdapContext ctx, String userDN) throws NamingException
{
log.debug("Performing authorization.");
String principalRoleAttributeName = (String) options.get(PRINCIPAL_ROLE_ATTRIBUTE_NAME);
if(principalRoleAttributeName == null) principalRoleAttributeName="memberOf";
String roleNameAttributeName = (String) options.get(ROLE_NAME_ATTRIBUTE_NAME);
if(roleNameAttributeName == null) roleNameAttributeName="cn";
// we want only the attribute containing the roles returned
String[] getattrs = {principalRoleAttributeName};
// get the attributes for the user
Attributes attrs = ctx.getAttributes(userDN,getattrs);
Attribute attr = attrs.get(principalRoleAttributeName);
if(attr !=null){
NamingEnumeration roles = attr.getAll();
while (roles.hasMore())
{
String roleName = (String)roles.next();
// we want only the attribute containing the role name returned
String[] rgetattrs = {roleNameAttributeName};
// get the attributes for the role
Attributes rattrs = ctx.getAttributes(roleName,rgetattrs);
Attribute rattr = rattrs.get(roleNameAttributeName);
try
{
String shortRoleName = (String)rattr.get();
Principal p = super.createIdentity(shortRoleName);
log.debug("Assign user to role " + shortRoleName + " from role DN " + roleName);
userRoles.addMember(p);
//add recursive search here to search indirect roles
setupUserRoles(ctx, roleName );
}
catch(Exception x)
{
x.printStackTrace();
}
}
}
}"zparticle" wrote:
I've recently written a new login module for use with ActiveDirectory. Our schema didn't work with the LdapLoginModule at all. Perhaps this will help you. Our directory is set up a bit odd so I had write something new. Our AD is split into locations, for instance Building #1, Building #2. There is no single place in our AD to find users. We want the Windows network login and password to be used to log into the applications but the users are entered into AD with their full names. The network id is an attribute.
The code below handles the authentication and authorization of the users in AD. see the comments for an example of the login-config and how to use the various parameters./* * JBoss, the OpenSource WebOS * * Distributable under LGPL license. * See terms of license at gnu.org. */ //package org.jboss.security.auth.spi; import java.security.acl.Group; import java.security.Principal; import java.util.Iterator; import java.util.Map.Entry; import java.util.Properties; import java.util.Set; import java.util.StringTokenizer; import javax.naming.Context; import javax.naming.NamingEnumeration; import javax.naming.NamingException; import javax.naming.directory.Attribute; import javax.naming.directory.Attributes; import javax.naming.directory.BasicAttributes; import javax.naming.directory.SearchResult; import javax.naming.ldap.InitialLdapContext; import javax.security.auth.login.LoginException; import org.jboss.security.SimpleGroup; import org.jboss.security.SimplePrincipal; import org.jboss.security.auth.spi.UsernamePasswordLoginModule; /** * An implementation of LoginModule that authenticates against an LDAP server * using JNDI, based on the configuration properties. * <p> * The LoginModule options include whatever options your LDAP JNDI provider * supports. Examples of standard property names are: * <ul> * <li><code>Context.INITIAL_CONTEXT_FACTORY = "java.naming.factory.initial"</code> * <li><code>Context.SECURITY_PROTOCOL = "java.naming.security.protocol"</code> * <li><code>Context.PROVIDER_URL = "java.naming.provider.url"</code> * <li><code>Context.SECURITY_AUTHENTICATION = "java.naming.security.authentication"</code> * </ul> * <p> * The Context.SECURITY_PRINCIPAL is set to the distinguished name of the user * as obtained by the callback handler. * <p> * Additional module properties include: * <ul> * <li>principalSerachStartDNs : A ; delimited list of DNs to search under to find the * user. * <p> * OU=Users,OU=IT,OU=Departments,OU=Building 1,DC=company,DC=com;OU=Users,OU=Finance,OU=Departments,OU=Building 1,DC=company,DC=com; * <p> * an opaque Object using the <code>org.jboss.security.plugins.ObjectCallback</code> type * of Callback rather than as a char[] password using a JAAS PasswordCallback. * <li>principalIDAttributeName : The name of the attribute that is the id to match an object as the user * that is logging in. If the user logs in with the network id then sAMAccountName should be used here. * <li>principalSearchObjectClass : Used to narrow the list of matching objects to a specific type during the * search. Uses the objectClass attribute. This is typically set to user. * <li>principalRoleAttributeName : The name of the attribute that contains the user roles * <li>roleNameAttributeName : The name of the attribute in the role CN that is the name of the role for the user * <li>performAuthentication : Defaults to true, if false the users password is not verified. The users roles are set up. * * This class was based on the LdapLoginModule class by Scott.Stark@jboss.org. * * <p>Example login-config.xml<p> <pre> <application-policy name = "ldap"> <authentication> <login-module code = "ActiveDirectoryLoginModule" flag = "required" > <module-option name="java.naming.factory.initial">com.sun.jndi.ldap.LdapCtxFactory</module-option> <module-option name="java.naming.provider.url">ldap://127.0.0.1:389</module-option> <module-option name="java.naming.security.authentication">simple</module-option> <module-option name="java.naming.security.principal">CN=ldap,OU=Service Accounts,DC=company,DC=com</module-option> <module-option name="java.naming.security.credentials">password</module-option> <module-option name="principalSearchStartDNs"> OU=Users,OU=IT,OU=Departments,OU=Interlocken 380,DC=mcdata,DC=com </module-option> <module-option name="principalIDAttrbuteName">sAMAccountName</module-option> <module-option name="principalSearchObjectClass">user</module-option> <module-option name="principalRoleAttributeName">memberOf</module-option> <module-option name="roleNameAttributeName">cn</module-option> </login-module> </authentication> </application-policy> </pre> * * @author Scott.Shaver@mcdata.com */ public class ActiveDirectoryLoginModule extends UsernamePasswordLoginModule { private static final String PRINCIPAL_SEARCH_DNS = "principalSearchStartDNs"; private static final String PRINCIPAL_ID_ATTRIBUTE_NAME = "principalIDAttributeName"; private static final String PRINCIPAL_SEARCH_OBJECT_CLASS = "principalSearchObjectClass"; private static final String PRINCIPAL_ROLE_ATTRIBUTE_NAME = "principalRoleAttributeName"; private static final String ROLE_NAME_ATTRIBUTE_NAME = "roleNameAttributeName"; private static final String PERFORM_AUTHENTICATION = "performAuthentication"; public ActiveDirectoryLoginModule() { } private transient SimpleGroup userRoles = new SimpleGroup("Roles"); /** Overriden to return an empty password string as typically one cannot obtain a user's password. We also override the validatePassword so this is ok. @return and empty password String */ protected String getUsersPassword() throws LoginException { return ""; } /** Overriden by subclasses to return the Groups that correspond to the to the role sets assigned to the user. Subclasses should create at least a Group named "Roles" that contains the roles assigned to the user. A second common group is "CallerPrincipal" that provides the application identity of the user rather than the security domain identity. @return Group[] containing the sets of roles */ protected Group[] getRoleSets() throws LoginException { Group[] roleSets = {userRoles}; return roleSets; } /** Validate the inputPassword by creating a ldap InitialContext with the SECURITY_CREDENTIALS set to the password. @param inputPassword the password to validate. @param expectedPassword ignored */ protected boolean validatePassword(String inputPassword, String expectedPassword) { boolean isValid = false; String ldapAccount = (String)options.get("java.naming.security.principal"); Object ldapAccountPW = options.get("java.naming.security.credentials"); String performAuthentication = (String) options.get(PERFORM_AUTHENTICATION); if(performAuthentication == null) performAuthentication="true"; boolean doAuth = "true".equals(performAuthentication.toLowerCase()); if (doAuth && inputPassword != null) { log.debug("Performing authentication."); // See if this is an empty password that should be disallowed if (inputPassword.length() == 0) { super.log.debug("Rejecting empty password"); return false; } try { // first log in as the account to use to search with and find the user DN InitialLdapContext ctx = createLdapInitContext(ldapAccount, ldapAccountPW); if(ctx!=null) // we logged in now go find the user { String username = getUsername(); String userDN = findUser(ctx,username); ctx.close(); if(userDN!=null) { // Validate the password by trying to create an initial context InitialLdapContext uctx = createLdapInitContext(userDN, inputPassword); setupUserRoles(uctx,userDN); uctx.close(); isValid = true; } } } catch (NamingException e) { super.log.debug("Failed to validate password", e); } } else if(!doAuth) // don't validate the password just set up the roles { log.debug("Skipping authentication."); isValid = true; try { // first log in as the account to use to search with and find the user DN InitialLdapContext ctx = createLdapInitContext(ldapAccount, ldapAccountPW); if(ctx!=null) // we logged in now go find the user { String username = getUsername(); String userDN = findUser(ctx,username); if(userDN!=null) setupUserRoles(ctx,userDN); ctx.close(); } } catch (NamingException e) { super.log.debug("Failed to validate password", e); } } return isValid; } /** * @param ctx The ldap context * @param userDN String containing the full DN for the user */ private void setupUserRoles(InitialLdapContext ctx, String userDN) throws NamingException { log.debug("Performing authorization."); String principalRoleAttributeName = (String) options.get(PRINCIPAL_ROLE_ATTRIBUTE_NAME); if(principalRoleAttributeName == null) principalRoleAttributeName="memberOf"; String roleNameAttributeName = (String) options.get(ROLE_NAME_ATTRIBUTE_NAME); if(roleNameAttributeName == null) roleNameAttributeName="cn"; // we want only the attribute containing the roles returned String[] getattrs = {principalRoleAttributeName}; // get the attributes for the user Attributes attrs = ctx.getAttributes(userDN,getattrs); Attribute attr = attrs.get(principalRoleAttributeName); NamingEnumeration roles = attr.getAll(); while (roles.hasMore()) { String roleName = (String)roles.next(); // we want only the attribute containing the role name returned String[] rgetattrs = {roleNameAttributeName}; // get the attributes for the role Attributes rattrs = ctx.getAttributes(roleName,rgetattrs); Attribute rattr = rattrs.get(roleNameAttributeName); try { String shortRoleName = (String)rattr.get(); Principal p = super.createIdentity(shortRoleName); log.debug("Assign user to role " + shortRoleName + " from role DN " + roleName); userRoles.addMember(p); } catch(Exception x) { x.printStackTrace(); } } } /** * @param ctx The ldap context * @param username The name the user used to log in * @return String containing the full DN for the user */ private String findUser(InitialLdapContext ctx, String username) throws NamingException { String userDN = null; // get all of the starting search points String principalSearchStartDNs = (String) options.get(PRINCIPAL_SEARCH_DNS); if (principalSearchStartDNs == null) principalSearchStartDNs = ""; StringTokenizer st = new StringTokenizer(principalSearchStartDNs,";"); String[] searchHere = new String[st.countTokens()]; int index=0; while(st.hasMoreTokens()) searchHere[index++]=st.nextToken(); String principalIDAttributeName = (String) options.get(PRINCIPAL_ID_ATTRIBUTE_NAME); if (principalIDAttributeName == null) principalIDAttributeName = "sAMAccountName"; String principalSearchObjectClass = (String) options.get(PRINCIPAL_SEARCH_OBJECT_CLASS); if(principalSearchObjectClass == null) principalSearchObjectClass="user"; // set up the attrs to make a match on BasicAttributes matchAttrs = new BasicAttributes(true); matchAttrs.put(principalIDAttributeName, username); matchAttrs.put("objectClass", principalSearchObjectClass); for(int searchAt=0;searchAt<searchHere.length && userDN==null;searchAt++) { super.log.debug("Searching At: "+searchHere[searchAt]); NamingEnumeration answer = ctx.search(searchHere[searchAt], matchAttrs); while (answer.hasMore() && userDN==null) { super.log.debug("Found a result"); SearchResult sr = (SearchResult) answer.next(); Attributes attrs = sr.getAttributes(); Attribute dn = attrs.get("distinguishedName"); userDN = (String)dn.get(); } } super.log.debug("Returning userDN: "+userDN); return userDN; } private InitialLdapContext createLdapInitContext(String username, Object credential) throws NamingException { Properties env = new Properties(); // Map all option into the JNDI InitialLdapContext env Iterator iter = options.entrySet().iterator(); while (iter.hasNext()) { Entry entry = (Entry) iter.next(); env.put(entry.getKey(), entry.getValue()); } // Set defaults for key values if they are missing String factoryName = env.getProperty(Context.INITIAL_CONTEXT_FACTORY); if (factoryName == null) { factoryName = "com.sun.jndi.ldap.LdapCtxFactory"; env.setProperty(Context.INITIAL_CONTEXT_FACTORY, factoryName); } String authType = env.getProperty(Context.SECURITY_AUTHENTICATION); if (authType == null) env.setProperty(Context.SECURITY_AUTHENTICATION, "simple"); String protocol = env.getProperty(Context.SECURITY_PROTOCOL); String providerURL = (String) options.get(Context.PROVIDER_URL); if (providerURL == null) providerURL = "ldap://localhost:" + ((protocol != null && protocol.equals("ssl")) ? "636" : "389"); env.setProperty(Context.PROVIDER_URL, providerURL); env.setProperty(Context.SECURITY_PRINCIPAL, username); env.put(Context.SECURITY_CREDENTIALS, credential); super.log.debug("Logging into LDAP server, env=" + env); InitialLdapContext ctx = new InitialLdapContext(env, null); super.log.debug("Logged into LDAP server, " + ctx); return ctx; } }
-
6. Re: Role-mapping with LdapLoginModule and ActiveDirectory
zparticle Feb 15, 2005 1:26 PM (in response to logi)Nice fix, thanks. Those problems hadn't shown themselves here yet. We actually rely on the first one to know if something is hosed in the LDAP.
-
7. Re: Role-mapping with LdapLoginModule and ActiveDirectory
mbian Feb 15, 2005 2:25 PM (in response to logi)Just start to look at jboss recently, not really familiar with LDAP.
I believe you could pass REFERRAL="follow", so you could search from top without specify your mutiple user-containers."zparticle" wrote:
Nice fix, thanks. Those problems hadn't shown themselves here yet. We actually rely on the first one to know if something is hosed in the LDAP.