Announcement Announcement Module
Collapse
No announcement yet.
Can't do ldap bind authentication with domain\firstname.lastname Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Can't do ldap bind authentication with domain\firstname.lastname

    I get the following ldap.core.TokenMgrError when I try to do an ldap bind on a domain with "xyz\first.last" as the username. The bind itself works properly via ldp.exe and JXPlorer. Our Active Directory doesn't allow anonymous binds, so we have to use the user's login credentials on every ldap call (in this case authentication). I'm using Spring LDAP 1.3 with Spring Security 3.0.2. Below is my config (obfuscated to protect).

    Ideally I don't even want to have to parse the DN to bind. I want to bind with the domain\username. But I can't find any method on LdapTemplate or DirContextOperations to do that directly. If this needs to be posted in the spring security forum I'll do that, but the exception I'm getting comes from spring ldap so I figured here's the right place to post.

    Error: Your login attempt was not successful, try again.

    Reason: Failed to parse DN; nested exception is org.springframework.ldap.core.TokenMgrError: Lexical error at line 1, column 9. Encountered: "." (46), after : "".
    Reason: Failed to parse DN; nested exception is org.springframework.ldap.core.TokenMgrError: Lexical error at line 1, column 4. Encountered: "\\" (92), after : "".

    Code:
    2010-03-23 17:28:12,179 [http-8080-2] DEBUG authentication.UsernamePasswordAuthenticationFilter  - Authentication request failed: org.springframework.security.authentication.AuthenticationServiceException: Failed to parse DN; nested exception is org.springframework.ldap.core.TokenMgrError: Lexical error at line 1, column 9.  Encountered: "." (46), after : ""

    Here's the applicationContext-security.xml:

    Code:
    <?xml version="1.0" encoding="UTF-8"?>
    <beans:beans xmlns="http://www.springframework.org/schema/security"
        xmlns:beans="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
                            http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd">
    
        <global-method-security pre-post-annotations="enabled">
            <!-- AspectJ pointcut expression that locates our "post" method and applies security that way
            <protect-pointcut expression="execution(* bigbank.*Service.post*(..))" access="ROLE_TELLER"/>
            -->
        </global-method-security>
    
    	<http use-expressions="true">
    		<!-- intercept-url pattern="/secure/extreme/**" access="hasRole('ROLE_SUPERVISOR')"/ --> 
    		<intercept-url pattern="/phase/**" access="hasRole('ROLE_ADMIN')"/>
    		<intercept-url pattern="/summary/**" access="isAuthenticated()" />
    		<intercept-url pattern="/login**" access="permitAll"/>
    		<intercept-url pattern="/spring_security_login" access="permitAll"/>
    		<intercept-url pattern="/images/**" access="permitAll"/>
    		<intercept-url pattern="/js/**" access="permitAll"/>
    		<intercept-url pattern="/css/**" access="permitAll"/>
    		<intercept-url pattern="/yaml/**" access="permitAll"/>
    		<intercept-url pattern="/**" access="isAuthenticated()" />
    
          <form-login login-page="/login.jsp" 
          	login-processing-url="/j_spring_security_check"
          	authentication-failure-url="/login.jsp?login_error=1" 
          	always-use-default-target="true"
          	default-target-url="/"
          	/>
          <logout logout-success-url="/"/>
    	</http>
    
    
        <authentication-manager>
            <authentication-provider ref='ldapProvider'/>
        </authentication-manager>
    
        <beans:bean id="ldapProvider" class="org.springframework.security.ldap.authentication.LdapAuthenticationProvider">
            <beans:constructor-arg ref="bindAuthenticator"></beans:constructor-arg>
            <beans:constructor-arg ref="authoritiesPopulator"></beans:constructor-arg>
        </beans:bean>
    
    	<beans:bean id="bindAuthenticator" class="my.security.ldap.authentication.BindAuthenticator">
    <!-- consider this to be the same exact thing as spring security's 
    BindAuthenticator, haven't modified  the authenticate or bindWithDn methods -->
    		<beans:constructor-arg ref="contextSource" />
    		<beans:property name="userDnPatterns">
    			<beans:list>
    				<beans:value>cn={0},OU=DevTest Users,DC=xyz,DC=com</beans:value>
    <!-- Both of these next two fail before even going into the BindAuthenticator -->				<beans:value>{0}</beans:value>
    				<beans:value>xyz\{0}</beans:value>
    				<beans:value>cn={0},OU=group,OU=mail,DC=xyz,DC=com</beans:value>
    			</beans:list>
    		</beans:property>
    	</beans:bean>
    
        <!-- Modified based on Example 8.1 of section 8.1.3.2. Custom Principal and Credentials Management in the Spring LDAP Reference doc -->
    	<beans:bean id="contextSource" 
    			class="org.springframework.security.ldap.DefaultSpringSecurityContextSource">
         <beans:constructor-arg value="ldap://activedirectoryhostname:9389/DC=xyz,DC=com"/>
         <beans:property name="authenticationSource" ref="springSecurityAuthenticationSource" />
    	</beans:bean>
    
       <beans:bean id="springSecurityAuthenticationSource"
          class="org.springframework.security.ldap.authentication.SpringSecurityAuthenticationSource" />
        
    	<beans:bean id="authoritiesPopulator" class="my.security.ldap.userdetails.myAuthoritiesPopulator">
    	    <beans:constructor-arg index="0" ref="contextSource" />
    	    <beans:constructor-arg index="1" value="ou=groups" />
    	    <beans:constructor-arg index="2" ref="gmsTemplate"/>
    	    <beans:property name="groupSearchFilter" value="(member={0})"/>
    	    <beans:property name="rolePrefix" value="ROLE_"/>
    	    <beans:property name="searchSubtree" value="true"/>
    	    <beans:property name="convertToUpperCase" value="true"/>
    	</beans:bean>
    
    	<beans:bean id="gmsTemplate" class="my.security.gms.GmsTemplate">
    		<beans:property name="baseUrl" value="http://localhost/GMS/Gms.svc"/>
    		<beans:property name="messageConverters">
    			<beans:list>
    				<beans:bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"/>
    			</beans:list>
    		</beans:property>
    	</beans:bean>
    
    </beans:beans>

  • #2
    I can trigger a part of your problem by sending an inadequately escaped DN to DistinguishedName:

    Fails:
    Code:
    DistinguishedName dn = new DistinguishedName("cn=Some\\Per-son.7,ou=company1,c=Sweden");
    
    org.springframework.ldap.core.TokenMgrError: Lexical error at line 1, column 8.  Encountered: "\\" (92), after : ""
    However, after escaping the backslashes properly, there are no problems with either dot or dash. This works:
    Code:
    DistinguishedName dn = new DistinguishedName("cn=Some\\\\Per-son.7,ou=company1,c=Sweden");
    The only conclusion I can come up with is (again) that Spring Security passes invalid DNs to Spring LDAP.

    Interestingly, using DistinguishedName as it was intended, namely to do the escaping for you, the problem with escaping goes away:

    Code:
    DistinguishedName dn = new DistinguishedName();
    dn.append("c", "Sweden");
    dn.append("ou", "company1");
    dn.append("cn", ",=+<>#;\\"); // all the special characters plus the escape character
    System.out.println(dn);
    
    cn=\,\=\+\<\>\#\;\\,ou=company1,c=Sweden
    Note that dn.getValue will give the unescaped value:

    Code:
    System.out.println(dn.getValue("cn"));
    ,=+<>#;\

    Comment


    • #3
      Originally posted by ulsa View Post
      The only conclusion I can come up with is (again) that Spring Security passes invalid DNs to Spring LDAP.
      Can someone tell me where exactly this takes place in spring security ldap? I can then debug/step through it and hope to fix it.
      Last edited by djKianoosh; Mar 25th, 2010, 10:13 AM.

      Comment


      • #4
        possibly either DefaultLdapUsernameToDnMapper.buildDn() or LdapUtils.getFullDn()?

        Comment


        • #5
          Jeff writes:
          In Spring Security 3.0, BindAuthenticator.java line 115 the call to ctx.getAttributes should be using fullDn instead of userDn; by using userDn the encoding in LdapEncoder never gets used.

          Side Note: I learned a lot about the internals of both Spring-LDAP and Spring-Security while hunting this one down. In a few places (BindAuthenticator.java is an example) I found the dividing line between Spring-LDAP and Spring-Security to be less than ideal.
          Perhaps that could point you in the right direction. It's likely the source of your problem as well.

          Comment


          • #6
            back at this.. stepping through it with a debugger, it's throwing the TokenMgrError in DistinguishedName.parse() on line 184: dn.parser.dn();

            Code:
            	/**
            	 * Parse the supplied String and make this instance represent the
            	 * corresponding distinguished name.
            	 * 
            	 * @param path the LDAP path to parse.
            	 */
            	protected void parse(String path) {
            		DnParser parser = DefaultDnParserFactory.createDnParser(unmangleCompositeName(path));
            		DistinguishedName dn;
            		try {
            			dn = parser.dn();
            		}
            		catch (ParseException e) {
            			throw new BadLdapGrammarException("Failed to parse DN", e);
            		}
            		catch (TokenMgrError e) {
            			throw new BadLdapGrammarException("Failed to parse DN", e);
            		}
            		this.names = dn.names;
            	}
            For some reason I don't have the source for that DnParserImpl class, so I can't see what it's doing. But going into it, in that 'path' variable, is a string that looks like: cn=first.last,OU=DevTest Users,DC=xyz,DC=com

            should it have been escaped by this point already? The last few lines of the trace went like so:
            DistinguishedName.parse(String) line: 184
            DistinguishedName.<init>(String) line: 140
            BindAuthenticator.bindWithDn(String, String, String) line: 95
            BindAuthenticator.authenticate(Authentication) line: 63
            LdapAuthenticationProvider.authenticate(Authentica tion) line: 252


            So really, the only other place it should have been escaped is in BindAuthenticator itself (that's a spring security class that I extended). Now, I thought the parser itself was the one that should have done the escaping, but if you tell me that it should be escaped before this, then please tell me there's an existing method that can do this for me

            Comment


            • #7
              From your example above, which uses the append() method, it eventually calls this:
              org.springframework.ldap.core.LdapEncoder.nameDeco de(String)

              So maybe I can use nameEncode() before I pass the path to new DistinguishedName() in BindAuthenticator?

              Comment


              • #8
                Be careful here. You should probably stay away from manually trying to escape a string into a full DN. The DistinguishedName#append method does this for you, but you need to have the individual parts at hand.

                The DistinguishedName constructor that takes a string representing a full DN simply assumes it's a correctly escaped and valid DN. I think you should focus on getting Spring Security to send a valid DN. Jeff stated something about the wrong variable was used in BindAuthenticator: "...ctx.getAttributes should be using fullDn instead of userDn". Wouldn't that affect you too?

                You don't see the source of DnParserImpl because it's a generated class. The DN parser is written in JavaCC.

                Comment


                • #9
                  ... I don't have the source for that DnParserImpl class, so I can't see what it's doing. But going into it, in that 'path' variable, is a string that looks like: cn=first.last,OU=DevTest Users,DC=xyz,DC=com
                  Interesting. The following test passes:

                  Code:
                  /**
                   * Test for http://forum.springsource.org/showthread.php?t=86640.
                   */
                  public void testDistinguishedNameWithDotParsesProperly() {
                     DistinguishedName name = new DistinguishedName("cn=first.last,OU=DevTest Users,DC=xyz,DC=com");
                     assertEquals("cn=first.last,ou=DevTest Users,dc=xyz,dc=com", name.toCompactString());
                  
                     // also testing the parse method, you know, to be sure... :)
                     DistinguishedName dn = new DistinguishedName();
                     dn.parse("cn=first.last,OU=DevTest Users,DC=xyz,DC=com");
                     assertEquals("first.last", dn.getValue("cn"));
                     assertEquals("DevTest Users", dn.getValue("ou"));
                     assertEquals("xyz", dn.getLdapRdn(1).getValue());
                     assertEquals("com", dn.getLdapRdn(0).getValue());
                  }

                  Comment


                  • #10
                    If I understand it correctly you want to use "DOMAIN\username" instead of "distinguished name" to bind to LDAP.

                    ...then your user can enter 'username' & 'password' rather than 'CN=Fred,OU,something,O=somethingelse' & 'password'

                    The normal answer to this is that you use a static (ie in your config file) account to bind to LDAP and then use this bind to lookup the DN that matches the username and then try and bind with the DN + password.

                    The problem I have is that our companies security policy doesnt allow us to store a static account, so I've been trying to figure out if its possible to bind using the username (aka sAMAccount) + password since as you point out jXplorer does it.

                    ...I think you're barking up the wrong tree to be trying to figure out why Spring doesnt like a DN of DOMAIN\username as the answer is because its not a DN! - you (we) need to find an alternative place to set the 'username' rather than setting the DN.

                    Regards

                    David Bevan

                    Comment


                    • #11
                      David you are exactly right on the money.

                      We want to authenticate with DOMAIN\username against our federated AD. What is also a complication is that samaccountname is not the actual user name. In fact, there's really no field in this AD that has the username property directly for all users (since this AD has trusts with several other domains). As you say, I can bind with DOMAIN\username against our AD with JXPlorer or Softerra ldap browser or ldp.exe.

                      How do you think we should attack this? I'm thinking in BindAuthenticator, we really can't use that bindWithDn method. I'd rather stay within the framework of what springsec provides, because the URL security and security tags are really nice and easy for configuring the rest of security within the app.

                      Also, we dont need to do authorization with AD. We have Role and other user info available via an internal restful web service (I'm using RestTemplate to get roles based on the username, as I have my own authorities populator class).

                      So right now, I just need Spring Security to bind with "DOMAIN\username" and password against our ldap server (Active Directory). Plain and simple. Can't use anonymous login, and can't use a manager dn to do lookups.

                      Comment


                      • #12
                        any resolution?

                        I am facing the exact same issue with attempting to bind against AD with domain\username. I have a similar config, but have tried everything, from simple ldap-server and auth-manager to full-blown explicit bean config, and nothing... So, would love to know if your issue was ever resolved?

                        Comment


                        • #13
                          Anyone figured a easy way for this?

                          I feel this page http://static.springsource.org/sprin...iguration.html has some information but not sure if this works or this other way is the way to go:
                          http://www.gigaspaces.com/wiki/displ...DAP+repository

                          Comment


                          • #14
                            This should be included in the Spring Security documentation.

                            I culled various bits of information from google searches and came up with the following working solution using Spring 3.0.3, Spring LDAP 1.3.1 and Spring Security 3.0.5.

                            applicationContext.xml
                            Code:
                            	<security:ldap-server id="adServer" 
                            		url="${security.authentication.ldap.server.url}"/>
                            	
                            	<security:authentication-manager alias="authenticationManager">
                            		<security:authentication-provider ref="adAuthProvider"/>
                            		<security:authentication-provider ref="anonymousAuthenticationProvider"/>
                            	</security:authentication-manager>
                            	
                            	<bean id="userContextMapper" class="com.blah.app.services.security.AdUserContextMapper"/>
                            
                            	
                            	<bean id="adAuthenticator" class="com.blah.app.services.security.AdAuthenticator">
                                	<property name="contextFactory" ref="adServer" />
                                    <property name="principalPrefix" value="" />
                            	</bean>
                            
                            	<bean id="adAuthProvider" class="org.springframework.security.ldap.authentication.LdapAuthenticationProvider">  
                            	    <constructor-arg ref="adAuthenticator"/>  
                            	    <property name="userDetailsContextMapper" ref="userContextMapper"/>  
                            	</bean>
                            AdAuthenticator.java
                            Code:
                            public class AdAuthenticator implements LdapAuthenticator
                            {
                            
                                private DefaultSpringSecurityContextSource _contextFactory;
                                private String _principalPrefix = "";
                            
                                public DirContextOperations authenticate(Authentication authentication)
                                {
                                    // Grab the username and password out of the authentication object.
                                    String principal = _principalPrefix + authentication.getName();
                                    String password = "";
                            
                                    if(authentication.getCredentials() != null)
                                    {
                                        password = authentication.getCredentials().toString();
                                    }
                            
                                    // If we have a valid username and password, try to authenticate.
                                    if(!("".equals(principal.trim())) && !("".equals(password.trim())))
                                    {
                                        _contextFactory.setPassword(password);
                                        _contextFactory.setUserDn(principal);
                                        
                                        InitialLdapContext ldapContext = (InitialLdapContext) _contextFactory.getReadWriteContext();
                                        
                                        // We need to pass the context back out, so that the auth provider
                                        // can add it to the
                                        // Authentication object.
                                        DirContextOperations authAdapter = new DirContextAdapter();
                                        authAdapter.addAttributeValue("ldapContext", ldapContext);
                            
                                        return authAdapter;
                                    }
                                    else
                                    {
                                        throw new BadCredentialsException("Blank username and/or password!");
                                    }
                                }
                            
                                public DefaultSpringSecurityContextSource getContextFactory() 
                                {
                                    return _contextFactory;
                                }
                            
                                /**
                                 * Set the context factory to use for generating a new LDAP context.
                                 * 
                                 * @param contextFactory
                                 */
                                public void setContextFactory(DefaultSpringSecurityContextSource contextFactory) 
                                {
                                    _contextFactory = contextFactory;
                                }
                            
                                public String getPrincipalPrefix() 
                                {
                                    return _principalPrefix;
                                }
                            
                                /**
                                 * Set the string to be prepended to all principal names prior to attempting authentication
                                 * against the LDAP server.  (For example, if the Active Directory wants the domain-name-plus
                                 * backslash prepended, use this.)
                                 * 
                                 * @param principalPrefix
                                 */
                                public void setPrincipalPrefix(String principalPrefix) 
                                {
                                    if(principalPrefix != null) 
                                    {
                                        _principalPrefix = principalPrefix;
                                    } 
                                    else 
                                    {
                                        _principalPrefix = "";
                                    }
                                }
                            }
                            AdUserContextMapper.java
                            Code:
                            public class AdUserContextMapper implements UserDetailsContextMapper
                            {
                                private static Log log = LogFactory.getLog(AdUserContextMapper.class);
                            
                                
                                @Override
                                public UserDetails mapUserFromContext(DirContextOperations p_dirContext, String p_userName, Collection<GrantedAuthority> p_authorities)
                                {
                                    String userName;
                                    String displayName = "";
                                    String mail = "";
                                    
                                    int index = 0;
                                    
                                    if( (index = p_userName.indexOf("\\")) != -1 )
                                        userName = p_userName.substring(index+1);
                                    else if( (index = p_userName.indexOf("@") ) != -1 )
                                        userName = p_userName.substring(0, index);
                                    else
                                        userName = p_userName;
                                    
                                    InitialLdapContext ldapContext = (InitialLdapContext)p_dirContext.getObjectAttribute("ldapContext");
                                    
                                    String returnedAtts[] ={ "displayName", "mail" };
                                    SearchControls sc = new SearchControls();
                                    sc.setReturningAttributes(returnedAtts);
                                    sc.setSearchScope(SearchControls.SUBTREE_SCOPE);
                                    
                                    try
                                    {
                                        NamingEnumeration<SearchResult> result = ldapContext.search("", "(&(objectclass=user)(sAMAccountName=" + userName + "))", sc);
                                        
                                        if( result.hasMoreElements() )
                                        {
                                            SearchResult sr = result.nextElement();
                                            
                                            Attributes attributes = sr.getAttributes();
                                            
                                            Attribute displayNameAttr = attributes.get("displayName");
                                            Attribute mailAttr = attributes.get("mail");
                                            
                                            if( displayNameAttr != null )
                                                displayName = (String)displayNameAttr.get();
                                            
                                            if( mailAttr != null )
                                                mail = (String)mailAttr.get();
                                        }
                                    }
                                    catch(Throwable e)
                                    {
                                        throw new RuntimeException("Failed to retrieve user attributes from ldap server. See wrapped exception for details.", e);
                                    }
                                    finally
                                    {
                                         LdapUtils.closeContext(ldapContext);
                                    }
                            
                                    Principal principal = new Principal();
                            
                                    principal.setUserName(p_userName);
                                    principal.setUserFullName(displayName);
                                    principal.setEmailAddress(mail);
                            
                            // fetch your granted authorities and set them here if they are
                            // not based on AD group membership.
                            //      principal.setGrantedAuthorities(new GrantedAuthority[]{"ROLE_USER"});
                            
                                    return principal;
                                }
                            
                                @Override
                                public void mapUserToContext(UserDetails p_userDetails, DirContextAdapter p_dirContext)
                                {
                                }
                            
                            }
                            I found AD would authenticate both <domain>\<username> and <username>@<dotted.domain.name.com>
                            Last edited by rshan; May 13th, 2011, 11:29 AM. Reason: Corrected code example

                            Comment


                            • #15
                              Hi ,

                              I tried to use the same code above, can you pl. let me know the imports for the classes. I am also facing the same issue for last 2 days.

                              Comment

                              Working...
                              X