jsfldaphttpsessionjboss-eap-6facescontext

What is the easiest way to get an LDAPContext from a FacesContext?


I've got a Java doLogin() method invoked from a JSF page that gets an id (String netId) and password (String password) from the user. doLogin() initiates the authentication using netId as the principal in an Active Directory login. After that, I would like to get other attributes besides principal name from the Directory that secures my app.

My security is configured in the container & it works, such that

HttpSession ses = FacesContext.getCurrentInstance().getExternalContext().getSession (false);
HttpServletRequest req = FacesContext.getCurrentInstance().getExternalContext().getRequest();

req.login(netID, password);

is successful and

req.getUserPrincipal().getName();

returns the user's netID. However, my app uses the netId only for authentication. Other attributes (commonName for example) are needed for other parts of the app that access another database. I want to do something like

usefulLDAPobj = *getLDAPSession from "somewhere" in the HTTP Session, the FacesContext or some other available object*

String cn = usefulLDAPobj.getAttributeFromProfile ("cn");

ses.setAttribute("username", cn);

and from then on use username, stored in the session, in my Hibernate ORM.

I know the simple-minded usefulLDAPobj.getAttributeFromProfile ("cn") will be more complex, but I can fill that out if I can find a starting point that gets me access to the LDAP Directory.

Since there is an obvious LDAP connection being set up by the container I feel there must be a way for me to make use of it without having to manually build up an LdapContext programatically; which would require the code to know all the LDAP server / bind-DN / bind-password configuration that the web server (JBoss EAP 6.2) already knows about (from the <login-module> defined in standalone.xml). For example, methods like getUserPrincipal() and isUserInRole() need access to the very same Directory profile that I want access to.

So my question is: is there a way to get an LDAP connection or context from a FacesContext or a HTTPServletRequest or any objects accessible from an HTTPServlet?


Solution

  • I think a useful answer to the question would be there's no way to get an LDAPContext directly from FacesContext, but by writing a container-specific Login Module and Principal class you can pass additional data via the HttpServletRequest that is obtained through FacesContext.

    I'll put the details of my solution here, because even though it's not directly related to FacesContext it gives me what I asked for in the body of the question, which is a way to get additional user data from the LDAP profile while avoiding the need to create a whole separate LDAPContext.

    What I wanted specifically was the CN, which I was able to parse out of the DN without doing an additional search. If I needed any other data, I assume that I could get that using ctx in findUserDN() below.

    I guess I am making my app dependent on JBoss with this solution, and if that were undesirable I would search for a JBoss-independent login module class to extend (no idea if that would be easy, difficult or impossible).

    Here's my solution:

    1. Override findUserDN (LdapContext ctx) in AdvancedADLoginModule

      package ca.mycompany.myapp.jboss;
      
      import java.security.Principal;
      
      import javax.naming.ldap.LdapContext;
      import javax.security.auth.login.LoginException;
      
      import org.jboss.security.negotiation.AdvancedADLoginModule;
      
      public class NameFetchingADLoginModule extends AdvancedADLoginModule
      
          @Override
          protected String findUserDN(LdapContext ctx) throws LoginException
          {
              String lclUserDN = super.findUserDN(ctx);
      
              Principal principal = getIdentity();
      
              if (principal instanceof PrincipalWithDisplayName)
              {
                  String displayName = lclUserDN.substring(3, lclUserDN.indexOf(','));
                  ((PrincipalWithDisplayName) principal).setDisplayName (displayName);
              }
      
              return lclUserDN;
          }
      }
      
    2. extend Principal to provide a displayName attribute

      package ca.mycompany.myapp.jboss;
      
      import java.io.Serializable;
      import java.security.Principal;
      
      public class PrincipalWithDisplayName implements Serializable, Principal
      {
          private static final long serialVersionUID = 1L;
          private final String name;
      
          // additional attribute provided by this subclass
          private String displayName;
      
          public PrincipalWithDisplayName(final String name) {
              this.name = name;
          }
      
          // new and overriding getters and setters, equals() and hashCode() removed for brevity
      }
      
    3. use the new login module and Principal in a doLogin() method

    snippet:

        String displayName = "";
        HttpSession ses = FacesContext.getCurrentInstance().getExternalContext().getSession (false);
        HttpServletRequest req = FacesContext.getCurrentInstance().getExternalContext().getRequest();
    
        try {           
            req.login(userName, password); // this throws an exception if authentication fails
    
            Principal lclUser = req.getUserPrincipal();
            if (lclUser instanceof PrincipalWithDisplayName)
            {
                displayName = ((PrincipalWithDisplayName) lclUser).getDisplayName ();
            }
    
            // get Http Session and store username
            //
            HttpSession session = HttpUtil.getSession();
            sess.setAttribute("username", displayName);
            ...
    
    1. Configure JBoss EAP 6.2, in standalone.xml, to use the new classes

    snippet:

    <subsystem xmlns="urn:jboss:domain:security:1.2">
        <security-domains>
            <security-domain name="company_ad" cache-type="default">
                <authentication>
                    <login-module code="ca.mycompany.myapp.jboss.NameFetchingADLoginModule" flag="required">
                        <module-option name="java.naming.factory.initial" value="com.sun.jndi.ldap.LdapCtxFactory"/>
                        <module-option name="java.naming.provider.url" value="ldap://servernm.mycompany.tst:389"/>
                        <module-option name="java.naming.security.authentication" value="simple"/>
                        <module-option name="bindDN" value="CN=AuthGuy,OU=Accounts,OU=Company User Accounts,DC=company,DC=tst"/>
                        <module-option name="bindCredential" value="Snowden1"/>
                        <module-option name="baseCtxDN" value="OU=Company User Accounts,DC=company,DC=tst"/>
                        <module-option name="baseFilter" value="(sAMnetID={0})"/>
                        <module-option name="searchScope" value="SUBTREE_SCOPE"/>
                        <module-option name="allowEmptyPassword" value="false"/>
                        <module-option name="rolesCtxDN" value="OU=Company User Accounts,DC=company,DC=tst"/>
                        <module-option name="roleFilter" value="(sAMAccountName={0})"/>
                        <module-option name="roleAttributeID" value="memberOf"/>
                        <module-option name="roleAttributeIsDN" value="true"/>
                        <module-option name="roleNameAttributeID" value="cn"/>
                        <module-option name="recurseRoles" value="1"/>
                        <module-option name="principalClass" value="ca.mycompany.myapp.jboss.PrincipalWithDisplayName"/>
                    </login-module>
                </authentication>
            </security-domain>
        </security-domains>
    </subsystem>