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.
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=Kumar\5c, 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
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