You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@shiro.apache.org by "Jochen Kemnade (JIRA)" <ji...@apache.org> on 2013/11/12 13:25:18 UTC
[jira] [Commented] (SHIRO-347) Improved JndiLdapRealm Concept
[ https://issues.apache.org/jira/browse/SHIRO-347?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=13820056#comment-13820056 ]
Jochen Kemnade commented on SHIRO-347:
--------------------------------------
I'm facing the same issue (users being organized into different OUs).
I worked around it by overriding the method {{protected String getUserDn(final String principal) throws IllegalArgumentException, IllegalStateException}} in my subclass of {{JndiLdapRealm}}. Unfortunately, neither is the method getting the system LDAP context nor may it throw a {{NamingException}}, so that makes the actual implementation a bit difficult.
> 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
> Labels: JndiLdapRealm, ldap, realm
> Fix For: 1.3.0
>
> Original Estimate: 2h
> Remaining Estimate: 2h
>
> 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 was sent by Atlassian JIRA
(v6.1#6144)