Skip to content
Advertisement

Ldap AD Authentication in Spring Security

I have a login page in my application where I want to validate the entered username/password against Ldap AD. I am thinking of creating a bind and get a context. If bind is successful that means user is authenticated. In Java I have achieved it like this:

public class Test {
    public static void main(String[] args) {
        try {

            Hashtable<String, String> env = new Hashtable<String, String>();
            env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
            env.put(Context.PROVIDER_URL, "ldap://ldapserver:389");
            env.put(Context.SECURITY_AUTHENTICATION, "simple");
            env.put(Context.SECURITY_PRINCIPAL, "domain\userId");  //coming from frontend login form
            env.put(Context.SECURITY_CREDENTIALS, password); // from login form

            LdapContext ctx = new InitialLdapContext(env, null);
            ctx.setRequestControls(null);
            NamingEnumeration<?> namingEnum = ctx.search("ou=users,ou=in,dc=global,dc=company,dc=org", "(objectclass=user)", getSimpleSearchControls());
            for (int i=0;i<1;i++) {
                SearchResult result = (SearchResult) namingEnum.next();
                Attributes attrs = result.getAttributes();
                NamingEnumeration<String> nam =attrs.getIDs();
                while(nam.hasMore()) {
                    System.out.println(nam.next());
                }
            }
            namingEnum.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    private static SearchControls getSimpleSearchControls() {
        SearchControls searchControls = new SearchControls();
        searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
        searchControls.setTimeLimit(30000);
        //String[] attrIDs = {"objectGUID"};
        //searchControls.setReturningAttributes(attrIDs);
        return searchControls;
    }
}

Above code is perfectly working for me. I want to implement the same in spring boot application using spring security. I have tried multiple suggested ways but getting some error every time.

Here I want to authenticate the enter username/password for which I think context binding is enough so not require to search that user again using something like “sAMAccountName={0}” please correct me if I am wrong.


Update

while trying below code, I can see in the logs that it fetches the user details but in the last giving some error:

      auth
        .ldapAuthentication()
        .userSearchFilter("(sAMAccountName={0})")
        .userDnPatterns()
        .userSearchBase("dc=global,dc=company,dc=org")
        .contextSource()
        .url("ldap://ldapserver")
        .port(389)
        .managerDn("domain\userId")
        .managerPassword("******");

Error Logs:

2022-08-04 19:47:44.477 TRACE 33541 --- [nio-8080-exec-2] o.s.s.w.a.www.BasicAuthenticationFilter  : Found username 'userId' in Basic Authorization header 
2022-08-04 19:47:44.478 TRACE 33541 --- [nio-8080-exec-2] o.s.s.authentication.ProviderManager     : Authenticating request with LdapAuthenticationProvider (1/1) 
2022-08-04 19:47:44.478 DEBUG 33541 --- [nio-8080-exec-2] o.s.s.l.a.LdapAuthenticationProvider     : Processing authentication request for user: userId 
2022-08-04 19:47:44.479 DEBUG 33541 --- [nio-8080-exec-2] o.s.s.l.s.FilterBasedLdapUserSearch      : Searching for user 'userId', with user search [ searchFilter: '(sAMAccountName={0})', searchBase: 'dc=global,dc=company,dc=org', scope: subtree, searchTimeLimit: 0, derefLinkFlag: false ] 
2022-08-04 19:47:44.831 DEBUG 33541 --- [nio-8080-exec-2] o.s.l.c.support.AbstractContextSource    : Got Ldap context on server 'ldap://ldapserver' 
2022-08-04 19:47:45.241 DEBUG 33541 --- [nio-8080-exec-2] o.s.s.ldap.SpringSecurityLdapTemplate    : Searching for entry under DN '', base = 'dc=global,dc=company,dc=org', filter = '(sAMAccountName={0})' 
2022-08-04 19:47:45.252 DEBUG 33541 --- [nio-8080-exec-2] o.s.s.ldap.SpringSecurityLdapTemplate    : Found DN: CN=Kumar, Rajesh,OU=Users,OU=IN,DC=global,DC=company,DC=org 
2022-08-04 19:47:45.253  INFO 33541 --- [nio-8080-exec-2] o.s.s.ldap.SpringSecurityLdapTemplate    : Ignoring PartialResultException 
2022-08-04 19:47:45.254 DEBUG 33541 --- [nio-8080-exec-2] o.s.s.l.a.BindAuthenticator              : Attempting to bind as cn=Kumar, Rajesh,ou=Users,ou=IN,dc=global,dc=company,dc=org 
2022-08-04 19:47:45.254 DEBUG 33541 --- [nio-8080-exec-2] s.s.l.DefaultSpringSecurityContextSource : Removing pooling flag for user cn=Kumar, Rajesh,ou=Users,ou=IN,dc=global,dc=company,dc=org 
2022-08-04 19:47:45.622 DEBUG 33541 --- [nio-8080-exec-2] o.s.l.c.support.AbstractContextSource    : Got Ldap context on server 'ldap://ldapserver' 
2022-08-04 19:47:45.623 DEBUG 33541 --- [nio-8080-exec-2] o.s.s.l.a.BindAuthenticator              : Retrieving attributes... 2022-08-04 19:47:45.625 DEBUG 33541 --- [nio-8080-exec-2] .s.s.l.u.DefaultLdapAuthoritiesPopulator : Getting authorities for user cn=Kumar, Rajesh,ou=Users,ou=IN,dc=global,dc=company,dc=org 
2022-08-04 19:47:45.627 DEBUG 33541 --- [nio-8080-exec-2] .s.s.l.u.DefaultLdapAuthoritiesPopulator : Searching for roles for user 'userId', DN = 'cn=Kumar, Rajesh,ou=Users,ou=IN,dc=global,dc= company,dc=org', with filter (uniqueMember={0}) in search base '' 
2022-08-04 19:47:45.628 DEBUG 33541 --- [nio-8080-exec-2] o.s.s.ldap.SpringSecurityLdapTemplate    : Using filter: (uniqueMember=cn=Kumar5c, Rajesh,ou=Users,ou=IN,dc=global,dc= company,dc=org) 
2022-08-04 19:47:45.628 DEBUG 33541 --- [nio-8080-exec-2] o.s.ldap.core.LdapTemplate               : The returnObjFlag of supplied SearchControls is not set but a ContextMapper is used - setting flag to true 
2022-08-04 19:47:45.629 DEBUG 33541 --- [nio-8080-exec-2] o.s.l.c.support.AbstractContextSource    : Got Ldap context on server 'ldap://ldapserver' 
2022-08-04 19:47:45.837 TRACE 33541 --- [nio-8080-exec-2] o.s.s.w.header.writers.HstsHeaderWriter  : Not injecting HSTS header since it did not match request to [Is Secure] 
2022-08-04 19:47:45.838 DEBUG 33541 --- [nio-8080-exec-2] w.c.HttpSessionSecurityContextRepository : Did not store empty SecurityContext 
2022-08-04 19:47:45.838 DEBUG 33541 --- [nio-8080-exec-2] s.s.w.c.SecurityContextPersistenceFilter : Cleared SecurityContextHolder to complete request 
2022-08-04 19:47:45.839 TRACE 33541 --- [nio-8080-exec-2] o.s.b.w.s.f.OrderedRequestContextFilter  : Cleared thread-bound request context: org.apache.catalina.connector.RequestFacade@1c138f13 
2022-08-04 19:47:45.847 ERROR 33541 --- [nio-8080-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception
    
org.springframework.ldap.NameNotFoundException: [LDAP: error code 32 - 0000208D: NameErr: DSID-03100217, problem 2001 (NO_OBJECT), data 0, best match of:   '' ]; 
nested exception is javax.naming.NameNotFoundException: [LDAP: error code 32 - 0000208D: NameErr: DSID-03100217, problem 2001 (NO_OBJECT), data 0, best match of:   '' ]; remaining name ''     
at org.springframework.ldap.support.LdapUtils.convertLdapException(LdapUtils.java:183) ~[spring-ldap-core-2.3.4.RELEASE.jar:2.3.4.RELEASE]  
at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:376) ~[spring-ldap-core-2.3.4.RELEASE.jar:2.3.4.RELEASE]     
at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:328) ~[spring-ldap-core-2.3.4.RELEASE.jar:2.3.4.RELEASE]     
at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:629) ~[spring-ldap-core-2.3.4.RELEASE.jar:2.3.4.RELEASE]     
at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:570) ~[spring-ldap-core-2.3.4.RELEASE.jar:2.3.4.RELEASE]

enter image description here

Can someone please guide me to migrate this plain java code to spring security.

Regards

Advertisement

Answer

We can see in the trace logs the following message :

DefaultLdapAuthoritiesPopulator : Searching for roles for user 'userId', DN = 'cn=Kumar, Rajesh,ou=Users,ou=IN,dc=global,dc= company,dc=org', with filter (uniqueMember={0}) in search base ''

The documentation says :

After authenticating the user successfully, the LdapAuthenticationProvider will attempt to load a set of authorities for the user by calling the configured LdapAuthoritiesPopulator.

The default implementation is trying to load the authorities by searching the directory for groups the user is a member of, but is failing to do so because you did not specify the group search base.

  auth
    .ldapAuthentication()
    .userSearchFilter("(sAMAccountName={0})")
    .userSearchBase("dc=global,dc=company,dc=org")
    .groupSearchBase("dc=global,dc=company,dc=org")  // <- here
    .contextSource()
    .url("ldap://ldapserver")
    .port(389)
    .managerDn("domain\userId")
    .managerPassword("******");

Usually, such groups are referenced under an ou=Roles component in the directory tree. For example, given the user search base defined in the “working” code : ou=roles,ou=in,dc=global,dc=company,dc=org, but a larger base (with only dc‘s) should be fine to start with.

It is worth noting that you can set a global base directly in the ldap url, and define relative dn for parameters where a dn is expected (with a valid global base set, leaving groupSearchBase empty wouldn’t throw an error), you could have for example :

    .userSearchBase("ou=users,ou=in")
    .groupSearchBase("ou=roles,ou=in")

with :

    .url("ldap://ldapserver:389/dc=global,dc=company,dc=org")

Also, note that you don’t need userDnPatterns() when using userSearchFilter(), use either one or the other.

@see Spring Security documentation : Loading Authorities

User contributions licensed under: CC BY-SA
8 People found this is helpful
Advertisement