LDAP Authentication using spring-security
Basic overview of LDAP and it's flow with spring-security
Introduction
LDAP is a very common technology used across organizations to store their employee's data. Windows Active Directory, Oracle's OID or open source OpenLDAP are some examples. Authentication is also one of its most common use cases.
Lightweight Directory Access Protocol (LDAP) is a language to interact with directory databases/ servers. It uses TCP/IP under the hood to communicate.
The directory server stores the information related to users in a hierarchical format. It stores their general attributes, groups, and passwords and provides us with operations like bind, unbind, add entry, delete entry, etc., and LDAP is the protocol to communicate with it.
How the information is stored on LDAP servers?
We store the information in the form of an inverted tree structure (root at the top). The root here is known as Domain Component (dc), which can have branches as per the policy design, grouping the users into Organizational Units (ou). These units will have users with all their attributes. This is referred to as DIT (Directory Information Tree).
Eg. Let's consider a user "Pranav" who is a developer in an organization named "Example". Below is the sample DIT in which his information will be stored on the LDAP server.
dc=example,dc=com
- ou=marketing
- user: x
- ou=finance
- user: y
- ou=engineering
- ou=QA
- user: z
- ou=developer
- user: Pranav
CommanName (cn): Pranav
SurName (sn): Joshi
UserID (uid): 143
UserName: pjoshi
email: gmail@pranav.com
Password: xxxxxxx
DistingueshedName (dn): cn=Pranav,ou=developer,ou=engineering,dc=example,dc=com
What is a DN?
Distinguished Name (dn) is an attribute that is used to uniquely identify each entry in the DIT on the LDAP server. It can be considered as an absolute path of an entry/ node.
Starting from the 'cn' navigating back to the 'dc', we can form the 'dn' of each entry. Like in the above example, the 'dn' of user Pranav is 'cn=Pranav,ou=developer,ou=engineering,dc=example,dc=com'.
Note: It is not compulsory to use 'cn', we can have any custom attribute (username, uid, etc.) to be part of 'dn', but 'cn' is most common.
LDAP Authentication
Spring offers to authenticate LDAP users using two ways -
Password Comparison: LDAP server often provides us with a password "compare" operation, where we can authenticate the user without actually retrieving the value of the password attribute. Or we can locally check the password by retrieving the attribute value.
Bind Authentication: This is the most common and secure way as the responsibility of authentication resides at the LDAP server without exposing the password to the clients. We just submit the username and password to the LDAP server, which will be authenticated and return some set of user attributes as requested, if authentication is successful.
Let's try here to understand the spring-security flow for authentication via LDAP Bind.
Spring-Security Flow for LDAP
UsernamePasswordAuthenticationFilter: HTTP request from the client is intercepted by this filter. Username and password are extracted to form a UsernamePasswordAuthenticationToken, which is further used by the authenticators. ProviderManager's authenticate() is called from here.
ProviderManager: It tries to authenticate using the supported AuthenticationProviders configured to it. If authentication fails, it tries using the next AuthenticationProviders and the exception from the last one is re-thrown if fails for all.
For LDAP we have configured LdapAuthenticationProvider.
LdapAuthenticationProvider: It mainly does two tasks
i. doAuthentication()
ii. loadUserAuthorities(): to obtain UserDetails which is then used to create a successful Authentication object.
BindAuthenticator: As mentioned earlier, I have implemented LDAP Authentication - the 'bind' way. BindAuthenticator will be called to 'doAuthentication'. LDAP bind operation will be attempted using the username and password. If authentication is successful, a Directory Context will be loaded with the fetched attributes, else an exception is thrown. If DN patterns are configured, it authenticates with them directly, else uses the configured search object to find the user and authenticates with the returned DN.
LdapUserDetailsMapper: To create a UserDetails object from the DirectoryContext, i.e to map the LDAP attributes (dn, cn, sn, etc.) with the spring-security understandable UserDetails object (which stores user information) this class is used.
LdapAuthoritiesPopulator: Granted authorities (spring-security understandable form) for an LDAP user is obtained
AbstractLdapAuthenticationProvider: createSuccessfulAuthentication() - finally the Authentication object is prepared using the UserDetails which is then set in the SecurityContextHolder indicating that this request is now authenticated.
Hope this gives you a high-level understanding of the flow. Spring Security offers us to customize these implementations and configure many more things.