Improved JndiLdapRealm Concept
------------------------------
Key: SHIRO-347
URL: https://issues.apache.org/jira/browse/SHIRO-347
Project: Shiro
Issue Type: New Feature
Components: Realms
Affects Versions: 1.2.0
Environment: No dependency
Reporter: Charles Syperski
Priority: Trivial
Fix For: 1.3.0
I am new to this framework, but I have a concept for extending the
JndiLdapRealm to make it more usable for larger LDAP trees. I have found the
JndiLdapRealm to be VERY limiting, especially since it only
allows a single OU due to the use of 'userDnTemplate'. I have extended the
JndiLdapRealm class to allow for sub-tree searches with a base OU as well as
customized search filters. This implementation probably isn't production
ready, and doesn't follow the coding standards of Shiro, it is more of a proof
of concept. Since I don't see a way to attach files/diffs, the source and
configuration for Shiro.ini is below:
== Start Source ==
package net.dupage88.usercentral.shiro;
import java.util.ArrayList;
import java.util.List;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.LdapContext;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.realm.ldap.JndiLdapRealm;
import org.apache.shiro.realm.ldap.LdapContextFactory;
import org.apache.shiro.realm.ldap.LdapUtils;
import org.apache.shiro.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* @author csyperski
*/
public class CWSJndiLdapRealm extends JndiLdapRealm
{
private static final Logger log =
LoggerFactory.getLogger(CWSJndiLdapRealm.class);
public static final String DEFAULT_SEARCH_FILTER = "(uid={0})";
private String searchBase;
private String searchFilter;
public CWSJndiLdapRealm()
{
super();
searchFilter = DEFAULT_SEARCH_FILTER;
searchBase = null;
}
@Override
public void setUserDnTemplate(String template) throws
IllegalArgumentException
{
throw new RuntimeException("This method is not implemented, please use
setSeachFilter and setBaseDn");
}
@Override
public String getUserDnTemplate()
{
throw new RuntimeException("This method is not implemented, please use
getSeachFilter and getBaseDn");
}
protected String getSearchFilter(String principal) throws
IllegalArgumentException, IllegalStateException
{
if (!StringUtils.hasText(principal))
{
throw new IllegalArgumentException("User principal cannot be null
or empty.");
}
if ( ! searchFilter.contains("{0}") )
{
log.warn("You didn't include {0} in your searchFilter, I assume you
know what you are doing!");
}
return searchFilter.replace("{0}", principal);
}
@Override
protected AuthenticationInfo queryForAuthenticationInfo(AuthenticationToken
token,
LdapContextFactory
ldapContextFactory)
throws NamingException {
Object principal = token.getPrincipal();
Object credentials = token.getCredentials();
if ( searchBase == null || searchBase.trim().length() == 0 )
{
log.error("searchBase must be defined");
return null;
}
if ( searchFilter == null || searchFilter.trim().length() == 0 )
{
log.error("searchFilter must be defined");
return null;
}
if ( ! (principal instanceof String) )
{
log.error("principal must be a string");
return null;
}
String filter = getSearchFilter((String)principal);
log.debug("Using base: {}", searchBase);
log.debug("Using filter: {}", filter);
LdapContext ctx = null;
SearchControls searchControls = new SearchControls();
searchControls.setReturningAttributes(new String[] { "dn" });
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
/* We don't know what the search filter will look like
* So if we get multiple results, we should try them all.
*/
List<String> possibleDns = new ArrayList<String>();
/* Lets search for the user first */
try
{
log.debug("Searching for user '{}' through LDAP", principal);
ctx = ldapContextFactory.getSystemLdapContext();
NamingEnumeration response = ctx.search(searchBase, filter,
searchControls);
while (response.hasMoreElements())
{
possibleDns.add(((SearchResult)response.next()).getNameInNamespace());
}
}
finally
{
LdapUtils.closeContext(ctx);
}
/* Now lets authenticate */
if ( ! possibleDns.isEmpty() )
{
for( String dn : possibleDns )
{
ctx = null;
try
{
log.debug("Attempting dn '{}' through LDAP", dn);
ctx = ldapContextFactory.getLdapContext(dn, credentials);
log.debug("Authenticated: {}!", dn);
return createAuthenticationInfo(token, principal,
credentials, ctx); // currently uses simple -> change to a subclass
thats supports holding the dn?
}
catch ( NamingException e )
{
log.debug("Failed to authenticate for: {}", dn);
}
finally
{
LdapUtils.closeContext(ctx);
}
}
}
throw new NamingException( "User: '" + (String)principal + "' not
authenticated!" );
}
/**
* @return the searchBase
*/
public String getSearchBase()
{
return searchBase;
}
/**
* @param searchBase the searchBase to set
*/
public void setSearchBase(String searchBase)
{
this.searchBase = searchBase;
}
/**
* @return the searchFilter
*/
public String getSearchFilter()
{
return searchFilter;
}
/**
* @param searchFilter the searchFilter to set
*/
public void setSearchFilter(String searchFilter)
{
this.searchFilter = searchFilter;
}
}
== End Source ==
== Configuration Details ==
Shiro.ini
ldapRealm = [yourpackagename].CWSJndiLdapRealm
# Removed the need for this below
# userDnTemplate limited the scope to a single OU (I believe)
# so it is replaced by searchBase and searchFilter
#ldapRealm.userDnTemplate = cn={0},ou=test,o=test
# This is the root of the LDAP search
ldapRealm.searchBase = o=test # NEW - The search root
# Search filter allows for more complex queries like
(&(cn={0})(objectClass=inetOrgPerson))
# or (&(uid={0})(objectClass=inetOrgPerson)(memberOf=somegroup))
ldapRealm.searchFilter = (cn={0})
ldapRealm.contextFactory.url = ldap://X.X.X.X:389
ldapRealm.contextFactory.authenticationMechanism = DIGEST-MD5
ldapRealm.contextFactory.systemUsername = cn=someuser,o=test
ldapRealm.contextFactory.systemPassword = somepassword
--
This message is automatically generated by JIRA.
If you think it was sent incorrectly, please contact your JIRA administrators:
https://issues.apache.org/jira/secure/ContactAdministrators!default.jspa
For more information on JIRA, see: http://www.atlassian.com/software/jira