8 Replies Latest reply on Aug 4, 2006 10:46 AM by jon_french

    OpenLDAP integration, just plan lost what to do after settin

    egandt

      I have A product which uses OpenLDAP for authorization, now I need the JBoss portal to also support LDAP authorization as to allow single sign on, now I have edited the login-config.xml with the and added a new policy named " <application-policy name="OpenLDAP">" and also I have added a unique user to the LDAP named JbossAdmin to use for administration, I already have about 20 other users from teh software I want to run on the Portal in the LDAP database.

      Since I have an existing schema for the software I modified the example policy so that it matches what I use in my application, but now I'm stuck. I can not find any information on how to modify the login portlet to authenticate against the LDAP server, so as to allow for single sign on, in fact I'm not even sure this is the next step to take?
      I'm must admit to feeling stupid, since I've setup WebLogic, SJES and WebSphere with LDAP intergration previously, however they SJES and WebSphere handle this without needing me to make any manual changes, and WebLogic ships with a sample login portlet and instructions on how to modify it to work, thus I'm unsure how to proceed.

      My hope is that someone can post an example to help me, and other like my self along with this task.


      Many thanks in advance,
      ERIC GANDT

        • 1. Re: OpenLDAP integration, just plan lost what to do after se
          jon_french

          You need to enable tomcat single sign on as specified here:

          http://docs.jboss.org/jbossas/jboss4guide/r4/html/cluster.chapt.html#clustering-http-sso

          Also, you need to read about security on JBOSS:

          http://docs.jboss.org/jbossas/jboss4guide/r4/html/ch8.chapter.html

          You need a LoginModule to perform your LDAP authentication, not the LoginPortlet.

          • 2. Re: OpenLDAP integration, just plan lost what to do after se
            egandt

            Ok I understand how to make use of the TOMCAT single sign-on, however I'm still confused about how JBOSS's LoginModule works?
            The JBOSS LoginModule, does not seem to be independent of the Application, now my application for which I can not modify the code (don't ask), authenticates againist LDAP on its own. The problem is when used wrapped in a portlet the portal needs to authenticate againist the same LDAP database, so all I want to do is replace the mechanism used to login againist the Portal database to one that works against LDAP, all other app-servers I've tried support this out of the box, so I'm confused as to why this seems so difficult in JBOSS. I just feel like there is something I'm missing?

            Looking around the web for a solution I found JOSSO which is designed to support single Sign-on with JBOSS, could this work with Portal?
            http://www.josso.org/

            Thanks,
            ERIC GANDT

            • 3. Re: OpenLDAP integration, just plan lost what to do after se
              egandt

              Ignoring JOSSO, All I want to do is have the Portal athenticate againist the LDAP server, instead of againist the DB server (PORTALDB), which was configured by default. I am not a Java Programmer, I am a systems intergrator, and in the past as I stated previously I have had no issues doing this, but then they other app servers provided examples.

              I have read and re-read chapter 8 of the Jboss manual and it means little to me beyond how to create an "application-policy", but it does not to over how to configure JBoss to to actually use the policy.

              The file is obviously included by jboss-service.xml, the jboss-service.xml file under security sets LoginConfig to call "jboss.security:service=XMLLoginConfig". login-config.xml contains numerous application policies, such as other, JBossWS ..., to which I added my own creation OpenLDAP. Once read none of these seem to ever be used again, also comparing Portal and non-portal the default login-config.xml files are identical, so I do not think the Portal is making any use of this to authenticate users.


              This again leads to the Portal interface and the User portlet, which authentcates againist the DB created by the portal using the tables:
              JBP_ROLES, JBP_ROLE_MEMBERSHIP, JBP_USERS... Now then I want to change the login to point to OpenLDAP instead of these tables, that it, nothing difficult or exciting, I just want to change the authenitcation mech. to use LDAP, simple right, something that can be found in the documentation correct, well call me stupid, but having wasted 3 days on this I've gotten nowhere and do not see a solution any place on the web. In fact I can only find a few topics on it in these forums and none with a valid sultion.

              Sorry to post yet again, but I have to believe that this has been done before and is documented by someone else, nor can I believe that I'm the only trying to do this,
              ERIC GANDT

              • 4. Re: OpenLDAP integration, just plan lost what to do after se
                jon_french

                I haven't used JOSSO, so I can't comment on it's usuage.

                However, did you read the chapter on jboss security? This link takes you to the section on the default LDAP login module:

                http://docs.jboss.org/jbossas/jboss4guide/r4/html/ch8.chapter.html#d0e18806

                Does your application (for which you cannot modify the code) use classic J2EE security architecture (i.e. a security-constraint specified in web.xml)? If so, you should be able to use tomcat sso.

                • 5. Re: OpenLDAP integration, just plan lost what to do after se
                  jon_french

                  We have a similar situation to yours and it took me a while to solve this problem too. I needed to authenticate against LDAP (username/password) but authorize (roles) against the JBoss Portal database. Is this similar to your issue or do you plan on getting role information from LDAP too?

                  In my case, I chained together LoginModules in login-config.xml. The first LoginModule contacts LDAP, authenticates the user, and inserts them into the portal database if they do not exist. (I wrote this LoginModule). The second LoginModule is the org.jboss.portal.core.security.jaas.ModelLoginModule from JBoss Portal that loads the user roles from the database.

                  I can provide more detail, but I wanted to know if this was similar to your problem.

                  • 6. Re: OpenLDAP integration, just plan lost what to do after se
                    egandt

                    Sounds similar. Current I have gotten the Portal to access the LDAP server, from what I can tell The group of an authorized user is "Authenticated", so I created a group in LDAP by this name and assigned 3 primary users to it, I then tested outside of the portal that the connect worked, once I try to connect from within the portal I get a 500 error
                    javax.servlet.ServletException: No such user No usch user jboss_admin
                    org.jboss.portal.server.servlet.PortalServlet.doGet(PortletServlet.java:227)

                    Yet I know this user exists and works using the basic security test from: [URL]http://wiki.jboss.org/wiki/Wiki.jsp?page=SecureAWebApplicationUsingACustomForm[\URL]
                    so I expect that the issue now has something to do with the groups and user setup of LDAP, saddly again there is no example of a working LDAP setup for Portal to work from, so I'm trying to work it out from the databse created for the Portal, so far its not going very well.

                    ERIC

                    • 7. Re: OpenLDAP integration, just plan lost what to do after se
                      egandt

                      No edit is annoying, but using any user other than jboss_admin even ones using the exact same groups work, Yet I do not understand why. Anyways I got a user to authenticate using LDAP with the portal interface, now I just need to get intergration to work correctly. Still Jon your solution would be helpful.

                      Thanks,
                      ERIC

                      • 8. Re: OpenLDAP integration, just plan lost what to do after se
                        jon_french

                        I was asked by Daniel Valero to post my authentication solution, so here it is. Note that this solution will work against both LDAP servers which allow an anonymous bind and those that do not.

                        In the example login-config.xml below, I've shown how to set up the LoginModule in the case in which you need a "service" LDAP account in order to do an initial bind to the LDAP server before attempting to bind the given username and password.

                        Also note that I'm assuming that the user is handing me a username of an email.

                        Thanks to Scott Dawson of Unisys for putting me on the right track with this solution.

                        The login-config.xml for JBoss Portal:

                        <?xml version='1.0'?>
                        <!DOCTYPE policy PUBLIC
                         "-//JBoss//DTD JBOSS Security Config 3.0//EN"
                         "http://www.jboss.org/j2ee/dtd/security_config.dtd">
                        <policy>
                         ...
                        
                         <!-- portal login policy -->
                         <application-policy name="portal">
                         <authentication>
                         <login-module code="org.jboss.security.auth.spi.LdapAuthenticatorLoginModule" flag="optional">
                         <module-option name="java.naming.provider.url">ldap://ldap.server.org:636/</module-option>
                         <module-option name="java.naming.security.protocol">ssl</module-option><!-- only required for LDAP over SSL -->
                         <module-option name="java.naming.security.principal">[insert DN here: ie. CN=My Name</module-option>
                         <module-option name="java.naming.security.credentials">[insert password here]</module-option>
                         <module-option name="searchBase">dc=mycompany,dc=net</module-option>
                         </login-module>
                        
                         <login-module code="org.jboss.portal.core.security.jaas.ModelLoginModule" flag="required">
                         <module-option name="unauthenticatedIdentity">guest</module-option>
                         <module-option name="hashAlgorithm">MD5</module-option>
                         <module-option name="hashEncoding">HEX</module-option>
                         <module-option name="userModuleJNDIName">java:/portal/UserModule</module-option>
                         <module-option name="additionalRole">Authenticated</module-option>
                         <!--
                         It is important that the useFirstPass option is set because
                         JbpLdapAuthenticatorLoginModule will set a different password in the
                         javax.security.auth.login.password shared module map than that which
                         was provided by the Subject if a user successfully authenticates via LDAP
                         -->
                         <module-option name="password-stacking">useFirstPass</module-option>
                         </login-module>
                         </authentication>
                         </application-policy>
                        </policy>
                        
                        


                        LdapAuthenticatorLoginModule.java

                        package org.jboss.security.auth.spi;
                        
                        import java.security.acl.Group;
                        import javax.security.auth.callback.CallbackHandler;
                        import javax.security.auth.Subject;
                        import javax.naming.InitialContext;
                        import javax.security.auth.login.LoginException;
                        
                        import java.util.Hashtable;
                        import java.util.Iterator;
                        import java.util.Map;
                        import java.util.Map.Entry;
                        
                        import org.jboss.security.SimpleGroup;
                        
                        import org.apache.commons.lang.StringUtils;
                        
                        import gov.doi.usgs.util.AuthenticationHelper;
                        
                        /**
                         * LoginModule that authenticates users based on an email and password lookup
                         * from an LDAP server. The following options must be configured for this module:
                         * <ul>
                         * <li>java.naming.provider.url - the url to the ldap server (ie. ldap://ldap.myserver.gov:389)</li>
                         * <li>searchBase - Use searchbase as the starting point for the LDAP search. [required]</li>
                         * </ul>
                         * The following options may be optionally configured for this module:
                         * <ul>
                         * <li>java.naming.security.principal - the distinguished name used to bind to the ldap server
                         * in order to perform the orginal email query. Leave blank for anonymous bind.</li>
                         * <li>java.naming.security.credentials - the password used to bind to the ldap sesrver in order
                         * to perform the orginal email query. Leave blank for anonymous bind.</li>
                         * <li>socketTimeout - How long should the module wait in millisecs for a connection to the LDAP server before it timesout.</li>
                         * <li>java.naming.security.protocol - set to "ssl" to use ssl encrpytion</li>
                         * </ul>
                         * <p>
                         * This LoginModule is used strictly for authentication (role assignment) and performs no authorization.
                         * Subsequent LoginModules should perform authorization.
                         * <p>
                         * The module works like this:
                         * <ol>
                         * <li>The module will attempt to find a node of objectClass=person and mail=[given username]
                         * by binding to the ldap server using the url, dn, and password configured in the module options</li>
                         * <li>If a matching dn is found, the module will then attempt to bind to the ldap server using
                         * the found dn and the given password.</li>
                         * <li>If the second bind is successful, the module will ensure that
                         * <ol>
                         * <li>the user's email and password are set into the LoginModule shareMemory map under
                         * the <tt>javax.security.auth.login.name</tt> and <tt>javax.security.auth.login.password</tt>
                         * keys respectively. This makes sure that all subsequent LoginModules that use password-stacking
                         * will use these values as the username/password instead of those provided by the CallbackHandler.</li>
                         * </ol>
                         * </li>
                         * </ol>
                         *
                         * @author Jon French
                         */
                        public class LdapAuthenticatorLoginModule extends UsernamePasswordLoginModule
                        {
                         public static final String SEARCH_BASE = "searchBase";
                         public static final String SOCKET_TIMEOUT = "socketTimeout";
                        
                         public LdapAuthenticatorLoginModule(){}
                        
                         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 "";
                         }
                        
                         /**
                         * Return a dummy set of userRoles. We don't care that they are not
                         * appropriately populated. The population of the user roles will be
                         * the responsibility of a second LoginModule.
                         * @return Group[] containing the sets of roles
                         */
                         protected Group[] getRoleSets() throws LoginException
                         {
                         Group[] roleSets = {userRoles};
                        
                         return roleSets;
                         }
                        
                         /**
                         * Returns true if the username and password validates against the
                         * configured LDAP options.
                         *
                         * @param inputPassword the password to validate.
                         * @param expectedPassword ignored
                         */
                         protected boolean validatePassword(String inputPassword, String expectedPassword)
                         {
                         boolean isValid = false;
                         if (inputPassword != null)
                         {
                         // See if this is an empty password that should be disallowed
                         if (inputPassword.trim().length() == 0)
                         {
                         return false; //never allow empty passwords
                         }
                         try
                         {
                         // Validate the password by trying to create an initial context
                         String username = getUsername();
                        
                         verifyLdapBind(username, inputPassword);
                        
                         sharedState.put("javax.security.auth.login.name",username);
                         sharedState.put("javax.security.auth.login.password",inputPassword);
                        
                         isValid = true;
                         }
                         catch (Exception e)
                         {
                         super.log.debug("Failed to validate password", e);
                         }
                         }
                         return isValid;
                         }
                        
                         /**
                         * Attempt to bind to the LDAP server configured in the module options and
                         * confirm that there [1] is a user with an mail attribute matching the given
                         * username and [2] the DN of that user will allow a success bind with the
                         * given credential as a String password.
                         *
                         * @throws Exception if either step [1] or [2] fails
                         */
                         private void verifyLdapBind(String username, Object credential)
                         throws Exception{
                        
                         Hashtable env = new Hashtable();
                        
                         // 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 the timeout in case an LDAP Server may not be connected */
                         String timeout = (String) options.get(SOCKET_TIMEOUT);
                         if (timeout == null) timeout = "2000";
                         env.put("com.sun.jndi.ldap.connect.timeout", timeout);
                        
                         switch(AuthenticationHelper.verifyLdapBind(env,username,(String) credential,(String)options.get(SEARCH_BASE))) {
                         case AuthenticationHelper.USERNAME_PASSWORD_AUTHENTICATED:
                         return;
                         case AuthenticationHelper.NO_USERNAME_FOUND:
                         throw new Exception("Failed to find dn for : " + username);
                         case AuthenticationHelper.INVALID_PASSWORD:
                         InitialContext _ctx = new InitialContext();
                         throw new Exception("Failed to validate password for : " + username);
                         }
                         }
                        
                        }
                        


                        Authentication Helper:

                        package gov.doi.usgs.util;
                        
                        import javax.naming.Context;
                        import javax.naming.NamingEnumeration;
                        import javax.naming.NamingException;
                        import javax.naming.ldap.LdapContext;
                        import javax.naming.directory.SearchResult;
                        import javax.naming.directory.SearchControls;
                        import javax.naming.ldap.InitialLdapContext;
                        
                        import java.util.Hashtable;
                        
                        import org.apache.commons.logging.Log;
                        import org.apache.commons.logging.LogFactory;
                        
                        /**
                         * Contains static methods to aid in Authentication coordination in the
                         * my.usgs.gov appliation
                         */
                        
                        public class AuthenticationHelper {
                        
                         private static final Log LOG = LogFactory.getLog(AuthenticationHelper.class);
                        
                         private static final String[] EMPTY_STR_ARRAY = new String[0];
                        
                         public final static int
                         USERNAME_PASSWORD_AUTHENTICATED = 100,
                         NO_USERNAME_FOUND = 1000,
                         INVALID_PASSWORD = 200;
                        
                         /**
                         * Returns an int which indicates if the given username and password
                         * authenticates against the LDAP server configured in the given Map.
                         * <p>
                         * The method will attempt to bind to the LDAP server configured by the
                         * <tt>envMap</tt> argument and confirm that there [1] is a user with an mail
                         * attribute matching the given email and [2] the DN of that user will allow
                         * a successful bind with the given credential as a String password.
                         *
                         * @param envMap A Hashtable that will be passed to the InitialLdapContext with the
                         * appropriately configured to do a initial bind to the ldap server configured
                         * in the map. The method will add the following properties to the Hashtable and thus
                         * override any prior configured map entries: Context.INITIAL_CONTEXT_FACTORY,
                         * Context.SECURITY_AUTHENTICATION
                         *
                         * @param email The email for which to search.
                         *
                         * @param searchBase The LDAP search base to use.
                         *
                         * @returns
                         * USERNAME_PASSWORD_AUTHENTICATED if the username/password successfully authenticates;
                         * NO_USERNAME_FOUND if the username was not found in the LDAP server;
                         * INVALID_PASSWORD if the username was found, but the password was invalid;
                         */
                         public static int verifyLdapBind(Hashtable envMap, String email, String password, String searchBase)
                         throws Exception {
                        
                         envMap.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.ldap.LdapCtxFactory");
                         envMap.put(Context.SECURITY_AUTHENTICATION,"simple");
                        
                         LdapContext ctx = null;
                        
                         try{
                        
                         if (LOG.isTraceEnabled()) {
                         LOG.trace("Logging into LDAP server, env=" + envMap);
                         }
                        
                         ctx = new InitialLdapContext(envMap,null);
                         SearchControls sc = new SearchControls();
                        
                         sc.setReturningAttributes(EMPTY_STR_ARRAY);
                         sc.setReturningObjFlag(true);// required to get the dn of the returned Context#getNameInNamespace() in the SearchResults
                        
                         //Specify the search scope
                         sc.setSearchScope(SearchControls.SUBTREE_SCOPE);
                        
                         //specify the LDAP search filter
                         String sf = "(&(objectClass=person)(mail=" + email + "))";
                        
                         sc.setCountLimit(1); // only one result needed if found.
                         sc.setTimeLimit(2000); // 2 second timelimit. -- Is this used??
                        
                         String dn = null;
                        
                         NamingEnumeration results = ctx.search(searchBase, sf, sc);
                        
                         //Loop through the search results
                         while (results.hasMoreElements()) {
                        
                         if (LOG.isTraceEnabled()) {
                         LOG.trace("Result found!");
                         }
                        
                         SearchResult sr = (SearchResult)results.next();
                        
                         dn = ((Context)sr.getObject()).getNameInNamespace();
                        
                         if (LOG.isTraceEnabled()) LOG.trace("SearchResult: " + sr);
                         }
                        
                         if (dn == null) {
                         return NO_USERNAME_FOUND;
                         } else {
                         /* Now that you have the dn, try to reconnect to the LDAP server
                         binding with the new DN and password. This will throw an
                         exception if the reconnect is not successuful*/
                         try {
                         ctx.addToEnvironment(Context.SECURITY_PRINCIPAL,dn);
                         ctx.addToEnvironment(Context.SECURITY_CREDENTIALS,password);
                         ctx.reconnect(ctx.getConnectControls());
                         } catch(NamingException e){
                         /*
                         If we are at this point, the user's email was found in the
                         LDAP server, but the provided password did not authenticate.
                         */
                         return INVALID_PASSWORD;
                         }
                         return USERNAME_PASSWORD_AUTHENTICATED;
                         }
                        
                         } finally{
                         if (ctx != null) ctx.close();
                         }
                         }
                        }