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]
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