8 Replies Latest reply on Oct 9, 2007 4:26 PM by shiva

    writing new LoginModul - unable to replace Principal ??

    Ralph Soika Apprentice

      Hi,

      I have a hard problem and need your help writing a new Login Modul for JBoss.
      I have the follwing situation: users authenticate against a Web Application using the LdapLoginModule. The WebApp calls Session EJBs which need the UserName to implement some Business logic.
      The Users can authenticate against the underlying LDAP server be a userid (e.g. customer ID) and password. - so far everything works well.
      But inside the EJBs the follwoing code returns the Loginname:

      public void setSessionContext(javax.ejb.SessionContext ctx) {
       String sUserName=ctx.getCallerPrincipal().toString());
       }
      


      But I need to get the Distinguished Name for the User (a full hierarchical name and not his UserID - "R15553" should become "CN=Ralph Soika/O=MYORG")

      So now I started to write my own LoginModul by subclassing the org.jboss.security.auth.spi.LdapLoginModule

      I tried everything to change the Principal with out any success.
      I hoocked into the getUsername(), createIdentity() and getUsernameAndPassword() method.
      I have read the source code and tried to understand the sharedState Map and put code like the following in any position:

      sharedState.put("javax.security.auth.login.name",sMyNewUserName);
      


      I also tried to implement a new Login CallbackHandler. But I find no way to replace the UserID, which the user typed into the Login Dialog of his browser, so that getCallerPrincipal() returns a Full Name.

      Can you give me please some assistance.
      When I success I will post my LoginModol here. I think this Problem could be helpfull for many JBoss developers.

      Thanks for help
      Ralph




        • 1. Re: writing new LoginModul - unable to replace Principal ??
          Mark P Ashworth Newbie

          Good Day,

          There are two parts this:
          1. Customise the Principal class
          2. Retrieving the CallingPrincipal

          1. Customise the Principal class
          By default the LoginModules in JBoss make use of the SimplePrincipal class to denote principals in the system. It is easy to change the principal that the LoginModules create by setting the principalClass of the LDAPLoginModule in login-conf.xml

          Example
          <module-option name="principalClass">za.co.connext.jboss.LDAPPrincipal</module-option>

          Just remember that the Principal class needs a constructor that takes the name of the user as a parameter.

          2. Retrieving the CallingPrincipal
          To enable to session context to retrieve the caller principal, the login module should save the information in the Subject.

          The easiest way is to create a SimpleGroup with the name CallerPrincipal and add your principal object to it. Add the CallerPrincipal group to the principals collection of the subject. You should check the principals collection first to determine if the CallerPrincipal group was added by a LoginModule further up the chain. All of this should be done in the commit method of the LoginModule. In my implementations when using JBoss is to always to do a super.commit() before adding my custom logic in the commit method.

          Hope this helps.

          • 2. Re: writing new LoginModul - unable to replace Principal ??
            Ralph Soika Apprentice

            Hi

            and thanks a lot for your Help!
            I works!

            I wrote the following new LDAPLogin Modul which converts the Login Name into the corresponding Distinguished name of the LDAP Directory the User authenticates :

            package org.imixs.jboss.security;
            
            import org.jboss.security.*;
            import org.jboss.security.auth.spi.LdapLoginModule;
            import javax.security.auth.login.LoginException;
            import java.util.*;
            import java.util.Map.Entry;
            import javax.security.auth.Subject;
            import javax.security.auth.callback.CallbackHandler;
            import javax.naming.*;
            import javax.naming.directory.*;
            import javax.naming.ldap.InitialLdapContext;
            
            /**
             * This LoginModul sublcasses the org.jboss.security.auth.spi.LdapLoginModul and
             * coverts a login name into the corresponding Distinguished Name of the LDAP
             * Object the login name belongs to. The Class replaces the CallerPricipal form
             * the Session Context so the new Distinguished Name will be returend by the
             * "getCallerPricipal()" method instead of the Login Name.
             *
             * The Class starts a LDAP Seach inside the LDAP Context from the JNDI Context
             * defined by the parameters form the
             * org.jboss.security.auth.spi.LdapLoginModul. Additional to configuration
             * parameters from the LdapLoginModul the following params can be set (all these
             * params are optional)
             *
             * LoginNameSeachContext the SeachContext to search for the UserObject of the
             * Login Name
             *
             * LoginNameSearchAttribute the Attribute to search. A SearchFilter is generated
             * in the Form "(LoginNameSearchAttribute=Username)"
             *
             * LoginNameToCompositeName if "false" the Name of the Search Result ist returnd
             * if "true" (not false) the Name will be converted into a ComposteName e.g.
             * "Ralph Soika,O=Imixs" -> "Ralph Soika/O=Imixs"
             *
             *
             * To replace the CallerPrinciapal inside the Session Context this class
             * overrides the commit Methode. The Commit Mehtode creates a SimpleGroup with
             * the name "CallerPrincipal" and adds a new SimplePricipal Objekct with the new
             * Distinguished Name to it. The CallerPrincipal group is then added to the
             * principals collection of the subject.
             *
             * (The current Implementation did not check the principals collection first to
             * determine if the CallerPrincipal group was added by a LoginModule further up
             * the chain!)
             *
             * @author Ralph Soika
             *
             */
            
            public class LdapLoginModuleDN extends LdapLoginModule {
             private static final String PRINCIPAL_DN_PREFIX_OPT = "principalDNPrefix";
            
             private static final String PRINCIPAL_DN_SUFFIX_OPT = "principalDNSuffix";
            
             private static final String SEARCH_SCOPE_OPT = "searchScope";
            
             private static final String SEARCH_TIME_LIMIT_OPT = "searchTimeLimit";
            
             // Environment Settings for DN Search
             private static final String LOGIN_NAME_SEARCH_CTX = "LoginNameSeachContext";
            
             private static final String LOGIN_NAME_SEARCH_ATTRIBUTE = "LoginNameSearchAttribute";
            
             private static final String LOGIN_NAME_TO_COMPOSITE_NAME = "LoginNameToCompositeName";
            
             String sUserPasword = "";
            
             public boolean commit() throws LoginException {
            
             // search User DN....
             String sDistinguishedName = "";
             try {
             sDistinguishedName = getDN(getUsername(), sUserPasword);
             if (sDistinguishedName == null || "".equals(sDistinguishedName))
             sDistinguishedName = getUsername();
             } catch (Exception edn) {
             // no dn found!
             sDistinguishedName = getUsername();
             }
            
             Set principals = subject.getPrincipals();
             SimpleGroup simpleGroup = new SimpleGroup("CallerPrincipal");
             if (principals.contains(simpleGroup))
             principals.remove(simpleGroup);
             simpleGroup.addMember(new SimplePrincipal(sDistinguishedName));
             principals.add(simpleGroup);
            
             return super.commit();
             }
            
             public void initialize(Subject subject, CallbackHandler callbackHandler,
             Map sharedState, Map options) {
             // System.out.println("[LdapLoginModuleDN] V 7.4");
            
             super.initialize(subject, callbackHandler, sharedState, options);
             }
            
             /**
             * This Method starts a search for the DN of the LDAP Object the Username
             * belogs to.
             *
             * @param username
             * @param credential
             * @return
             * @throws Exception
             */
             private String getDN(String username, Object credential) throws Exception {
             String sDN = "";
             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");
            
             String bindDN = (String) options.get(Context.SECURITY_PRINCIPAL);
             String bindCredential = (String) options
             .get(Context.SECURITY_CREDENTIALS);
             /*
             * String securityDomain = (String) options.get(SECURITY_DOMAIN_OPT);
             * if( securityDomain != null ) { ObjectName serviceName = new
             * ObjectName(securityDomain); char[] tmp =
             * DecodeAction.decode(bindCredential, serviceName); bindCredential =
             * new String(tmp); }
             */
            
             String principalDNPrefix = (String) options
             .get(PRINCIPAL_DN_PREFIX_OPT);
             if (principalDNPrefix == null)
             principalDNPrefix = "";
             String principalDNSuffix = (String) options
             .get(PRINCIPAL_DN_SUFFIX_OPT);
             if (principalDNSuffix == null)
             principalDNSuffix = "";
             String userDN = principalDNPrefix + username + principalDNSuffix;
             env.setProperty(Context.PROVIDER_URL, providerURL);
             env.setProperty(Context.SECURITY_PRINCIPAL, userDN);
             env.put(Context.SECURITY_CREDENTIALS, credential);
             if (bindDN != null) {
             // Rebind the ctx to the bind dn/credentials for the roles searches
             env.setProperty(Context.SECURITY_PRINCIPAL, bindDN);
             env.put(Context.SECURITY_CREDENTIALS, bindCredential);
             }
             // get Context
             InitialLdapContext ctx = new InitialLdapContext(env, null);
            
             try {
            
             int searchScope = SearchControls.SUBTREE_SCOPE;
             int searchTimeLimit = 10000;
             String timeLimit = (String) options.get(SEARCH_TIME_LIMIT_OPT);
             if (timeLimit != null) {
             try {
             searchTimeLimit = Integer.parseInt(timeLimit);
             } catch (NumberFormatException e) {
             }
             }
             String scope = (String) options.get(SEARCH_SCOPE_OPT);
             if ("OBJECT_SCOPE".equalsIgnoreCase(scope))
             searchScope = SearchControls.OBJECT_SCOPE;
             else if ("ONELEVEL_SCOPE".equalsIgnoreCase(scope))
             searchScope = SearchControls.ONELEVEL_SCOPE;
             if ("SUBTREE_SCOPE".equalsIgnoreCase(scope))
             searchScope = SearchControls.SUBTREE_SCOPE;
            
             SearchControls controls = new SearchControls();
             controls.setSearchScope(searchScope);
            
             controls.setTimeLimit(searchTimeLimit);
            
             String sContext = env.getProperty(LOGIN_NAME_SEARCH_CTX);
             if (sContext == null)
             sContext = "";
            
             String sLoginAttribute = env
             .getProperty(LOGIN_NAME_SEARCH_ATTRIBUTE);
             if (sLoginAttribute == null)
             sLoginAttribute = "uid";
            
             // create search filter
             String sFilter = "(" + sLoginAttribute + "=" + userDN + ")";
            
             NamingEnumeration answer = ctx.search(sContext, sFilter, controls);
             if (answer.hasMore()) {
             SearchResult sr = (SearchResult) answer.next();
             sDN = sr.getName();
            
             // convert into a composite name?
             String sConvert = env.getProperty(LOGIN_NAME_TO_COMPOSITE_NAME);
             if (!"false".equals(sConvert)) {
             CompositeName cn = new CompositeName();
             StringTokenizer st = new StringTokenizer(sDN, ",");
             while (st.hasMoreTokens())
             cn.add(st.nextToken().trim());
             sDN = cn.toString();
             }
            
             }
            
             } catch (Exception e) {
             System.out.println("[LdapLoginModuleDN] getDN error=" + e);
            
             }
             // System.out.println("[LdapLoginModuleDN] getDN Result='" + sDN + "");
            
             // Close the context to release the connection
             ctx.close();
             return sDN;
             }
            
             /**
             * this methode is overridden to save the current user password for later
             * ldap search
             */
             protected boolean validatePassword(String inputPassword,
             String expectedPassword) {
             sUserPasword = inputPassword;
             return super.validatePassword(inputPassword, expectedPassword);
             }
            
            }
            


            The Login Modul is configured as the LDAPLoginModul with 3 new optional params. So the following configuration works perfect with a IBM Domino LDAP Directory


            <application-policy name="imixsIX">
             <authentication>
             <login-module code="org.imixs.jboss.security.LdapLoginModuleDN"
             flag="required">
             <module-option name="java.naming.factory.initial">com.sun.jndi.ldap.LdapCtxFactory</module-option>
             <module-option name="java.naming.provider.url">ldap://mydominohost:389/</module-option>
            
            
             <module-option name="java.naming.security.authentication">simple</module-option>
             <module-option name="principalDNPrefix"></module-option>
             <!-- for principalDNSuffix no entry is needed for domino (e.g. o=MYDOMIAN) -->
             <module-option name="principalDNSuffix"></module-option>
             <module-option name="rolesCtxDN"></module-option>
             <module-option name="uidAttributeID">member</module-option>
             <module-option name="matchOnUserDN">true</module-option>
             <module-option name="roleAttributeID">cn</module-option>
             <module-option name="roleAttributeIsDN">false</module-option>
             <module-option name="searchTimeLimit">5000</module-option>
            
             <module-option name="searchScope">SUBTREE_SCOPE</module-option>
            
             <!-- Params for Distinguished Name Search (optional) -->
             <module-option name="LoginNameSeachContext"></module-option>
             <module-option name="LoginNameSearchAttribute">uid</module-option>
             <module-option name="LoginNameToCompositeName">true</module-option>
            
             </login-module>
             </authentication>
             </application-policy>
            


            I think this LoginModul will be usefull for J2EE Applications where the CallerPricipal is importend for business logic. So feel free to use it

            Thanks again
            ralph


            • 3. Re: writing new LoginModul - unable to replace Principal ??
              Mark P Ashworth Newbie

              Good Day,

              Your commit should only do your logic if the login was OK. Look at the LDAPLoginModule's parent class, you'll see that there is a protected variable `loginOK` which is set to true in the login() method and should be checked in the commit() method.

              • 4. Re: writing new LoginModul - unable to replace Principal ??
                Ralph Soika Apprentice

                hi, yes I have changed this. I also implememented a new version with extended functionality. Documentation and also source is available at:

                http://www.imixs.org/websites/imixs-org.nsf/chapter/0300.0100.0020.?OpenDocument

                This Modul works perfect with an IBM Lotus Domino LDAP and is able to resolve encapsulated roles!

                • 5. Re: writing new LoginModul - unable to replace Principal ??
                  shiva Newbie

                  Does it work with sun one ldap or active directory?????

                  • 6. Re: writing new LoginModul - unable to replace Principal ??
                    Ralph Soika Apprentice

                    hi
                    sorry I had only tested it with IBM Lotus Domino LDAP Directory

                    • 7. Re: writing new LoginModul - unable to replace Principal ??
                      shiva Newbie

                      I am trying to use with sun one ldap it is not working.Is there any Mbean similar to weblogic LDAPAuthenticatorMBean which can give me information about the ldap

                      • 8. Re: writing new LoginModul - unable to replace Principal ??
                        shiva Newbie

                        I am getting Bad password for username=admin
                        Below is the stack trace of the exception :

                        authInfo=AppConfigurationEntry[]:
                        [0]
                        LoginModule Class: org.jboss.security.ClientLoginModule
                        ControlFlag: LoginModuleControlFlag: required
                        Options:[1]
                        LoginModule Class: org.imixs.jboss.security.LdapLoginModuleExt
                        ControlFlag: LoginModuleControlFlag: required
                        Options:name=RoleSeachContext, value=ou=Groups, dc=axeda, dc=com
                        name=java.naming.security.principal, value=uid=admin, ou=Administrators, ou=TopologyManagement, o=NetscapeRoot
                        name=LoginNameSearch, value=(&(uid={0})(objectclass=person))
                        name=java.naming.factory.initial, value=com.sun.jndi.ldap.LdapCtxFactory
                        name=java.naming.security.credentials, value=admin
                        name=roleNameAttributeID, value=cn
                        name=roleFilter, value=(&(cn={0})(objectclass=groupofUniqueNames))]]</module-option>
                        <module-option name="baseFilter"><![CDATA[(&(uid={0})(objectclass=person))
                        name=java.naming.security.authentication, value=simple
                        name=java.naming.provider.url, value=ldap://leda.axeda.com:389/
                        name=roleAttributeID, value=cn
                        name=RoleSearch, value=(&(cn={0})(objectclass=groupofUniqueNames))
                        name=LoginNameSeachContext, value=ou=People, dc=axeda, dc=com
                        name=uidAttributeID, value=uniqueMember
                        name=LoginNameToCompositeName, value=true
                        name=roleAttributeIsDN, value=false



                        2007-10-09 16:19:42,694 TRACE [org.jboss.security.ClientLoginModule] Security domain: servicelinkdomain
                        2007-10-09 16:19:42,694 TRACE [org.jboss.security.ClientLoginModule] Enabling restore-login-identity mode
                        2007-10-09 16:19:42,694 TRACE [org.jboss.security.ClientLoginModule] Begin login
                        2007-10-09 16:19:42,694 TRACE [org.jboss.security.ClientLoginModule] Obtained login: admin, credential.class: [C
                        2007-10-09 16:19:42,694 TRACE [org.jboss.security.ClientLoginModule] End login
                        2007-10-09 16:19:42,725 DEBUG [org.imixs.jboss.security.LdapLoginModuleExt] Bad password for username=admin
                        2007-10-09 16:19:42,725 TRACE [org.jboss.security.ClientLoginModule] abort
                        2007-10-09 16:19:42,725 TRACE [org.jboss.security.SecurityAssociation] clear, server=true
                        2007-10-09 16:19:42,725 TRACE [org.jboss.security.plugins.JaasSecurityManager.servicelinkdomain] Login failure
                        javax.security.auth.login.FailedLoginException: Password Incorrect/Password Required
                        at org.jboss.security.auth.spi.UsernamePasswordLoginModule.login(UsernamePasswordLoginModule.java:213)
                        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
                        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
                        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
                        at java.lang.reflect.Method.invoke(Method.java:585)
                        at javax.security.auth.login.LoginContext.invoke(LoginContext.java:769)
                        at javax.security.auth.login.LoginContext.access$000(LoginContext.java:186)
                        at javax.security.auth.login.LoginContext$4.run(LoginContext.java:683)
                        at java.security.AccessController.doPrivileged(Native Method)
                        at javax.security.auth.login.LoginContext.invokePriv(LoginContext.java:680)
                        at javax.security.auth.login.LoginContext.login(LoginContext.java:579)
                        at org.jboss.security.plugins.JaasSecurityManager.defaultLogin(JaasSecurityManager.java:603)
                        at org.jboss.security.plugins.JaasSecurityManager.authenticate(JaasSecurityManager.java:537)
                        at org.jboss.security.plugins.JaasSecurityManager.isValid(JaasSecurityManager.java:344)
                        at org.jboss.web.tomcat.security.JBossSecurityMgrRealm.authenticate(JBossSecurityMgrRealm.java:491)
                        at org.apache.catalina.authenticator.FormAuthenticator.authenticate(FormAuthenticator.java:258)
                        at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:417)
                        at org.jboss.web.tomcat.security.JaccContextValve.invoke(JaccContextValve.java:84)
                        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:128)
                        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:104)
                        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
                        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:241)
                        at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:844)
                        at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:580)
                        at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:447)
                        at java.lang.Thread.run(Thread.java:595)
                        2007-10-09 16:19:42,725 TRACE [org.jboss.security.plugins.JaasSecurityManager.servicelinkdomain] End isValid, false


                        below is the configuration:

                        <login-module code="org.jboss.security.ClientLoginModule" flag="required"/>
                        <login-module code="org.imixs.jboss.security.LdapLoginModuleExt" flag="required">
                        <module-option name="java.naming.factory.initial">com.sun.jndi.ldap.LdapCtxFactory</module-option>
                        <module-option name="java.naming.provider.url">ldap://sample.sample.sample:389/</module-option>
                        <module-option name="java.naming.security.authentication">simple</module-option>
                        <module-option name="searchTimeLimit">5000</module-option>
                        <!-- searchScope is neccesary for Domino SUBTREE_SCOPE -->
                        <module-option name="searchScope"></module-option>
                        <!-- Params for Distinguished Name Search -->
                        <module-option name="LoginNameToCompositeName">false</module-option>
                        <module-option name="LoginNameSeachContext">ou=People, dc=axeda, dc=com</module-option>
                        <module-option name="LoginNameSearch"><![CDATA[(&(uid={0})(objectclass=person))]]></module-option>
                        <!-- Params for Role Search -->
                        <module-option name="roleAttributeID">cn</module-option>
                        <module-option name="RoleSeachContext">ou=Groups, dc=axeda, dc=com</module-option>
                        <module-option name="RoleSearch"><![CDATA[(&(cn={0})(objectclass=groupofUniqueNames))]]></module-option>
                        <!-- Principal und Credentials for ldap lookups -->
                        <module-option name="java.naming.security.principal">uid=admin, ou=Administrators, ou=TopologyManagement, o=NetscapeRoot</module-option>
                        <module-option name="java.naming.security.credentials">sample</module-option>
                        </login-module>