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?
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:
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;
}
}
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
}
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);
...
JBoss EAP 6.2
, in standalone.xml
, to use the new classessnippet:
<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>