7 Replies Latest reply on Feb 15, 2005 2:25 PM by mbian

    Role-mapping with LdapLoginModule and ActiveDirectory

    logi

      Hello,

      I'm authenticating users against an ActiveDirectory server using the LdapLoginModule and this seems to be working nicely. Given a correct username and password the appropriate principal can be retrieved from the SessionContext of my bean. (for debugging only of course)

      However, I've had little luck with mapping users to roles with this setup, probably because I don't fully understand what is going on. As an example, the user with the distinguished name

      CN=Logi Ragnarsson,OU=Tolvudeild,OU=Upplysinga & Taeknisvid,OU=Skrifstofa,OU=Notendur,DC=althingi,DC=is
      is authenticated, but will have an empty set of roles as seen by this exception:

      Insufficient method permissions, principal=logir, method=create, interface=HOME, requiredRoles=[pruf, Tolvudeild], principalRoles=[]


      The user has a number of role-mappings, including:
      memberOf CN=Tolvudeild,OU=Groups,DC=althingi,DC=is
      memberOf CN=pruf,OU=Groups,DC=althingi,DC=is


      either of which should be sufficient to allow access to the bean.

      My feeble attempts at setting this up have resulted in the following login module configuration as the only one in the login-config.xml file:

      <login-module code="org.jboss.security.auth.spi.LdapLoginModule" flag="required">
      <module-option name="debug">true</module-option>
      <module-option name="java.naming.provider.url">ldap://adc.althingi.is/</module-option>
      <module-option name="principalDNSuffix">@althingi.is</module-option>
      <module-option name="rolesCtxDN">OU=Groups,DC=althingi,DC=is</module-option>
      <module-option name="roleAttributeID">memberOf</module-option>
      <module-option name="roleAttributeIsDN">true</module-option>
      <module-option name="roleNameAttributeID">name</module-option>
      <module-option name="uidAttributeID">sAMAccountName</module-option>
      </login-module>


      I'm sure this is obvious to those who know what is going on, but I'm baffled.

        • 1. Re: Role-mapping with LdapLoginModule and ActiveDirectory
          zparticle

          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

            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

              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

                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

                  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

                    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

                      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.