Details
-
New Feature
-
Status: Open
-
Trivial
-
Resolution: Unresolved
-
1.2.0
-
No dependency
Description
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
}
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[]
);
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())
}
finally
/* 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
}
}
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