Spring Boot Security Configuration, practically explained – Part3: LDAP Bind Authentication
220527
Intro
This is the 3rd in a series of posts about the Spring Security customization based on the implementation of a custom class extending the WebSecurityConfigurerAdapter abstract class. You can access the previous posts below:
Notes:
The legacy Spring Boot Security Configuration (extending the WebSecurityConfigurerAdapter abstract class), has been deprecated and is being replaced by a component-based security configuration. However, since the existing coding base is huge, here, we are still using the WebSecurityConfigurerAdapter, and we will try to provide a better understanding, using practical examples.
If you don’t wish to continue seeing the warning curly yellow lines in your IDE when you use the latest versions of Spring Boot, you should use (switch to) an earlier version of Spring Security (spring.security dependency. Here is how you can do that.
LDAP Bind Authentication
General Prerequisites
Since we are going to focus on the subject, it is supposed that you have started having some familiarity with Spring Boot development, REST APIs, Maven dependency management and POM files, JSON, and other related subjects (Spring annotations, eg. @Beans, authentication, authorization, Spring validation, exception handling, DTOs, application properties, connecting to a database using JDBC, etc.). If you haven’t, then now is a good chance to start digging deeper in Spring / Spring Boot, far the best Java development framework.
LDAP Server
Since you can google and find dozens of posts regarding LDAP (Lightweight Directory Access Protocol), we are not going to go into details about that. Also, we are not going to use an embedded LDAP server, because a lot of posts present it. Our aim is to demonstrate how you can implement LDAP Authentication with the Spring Boot framework.
However, you have to have access to an LDAP Server, at least for testing purposes. This is because in this post we are not going to show you examples based on embedded LDAP Server (e.g. using the UnboundId open source LDAP server and the UnboundID LDAP SDK for Java. There are dozens of posts you can find googling the net (even in the official documentation here). A good option is to dedicate some time to installing the free Apache Directory Studio, an Eclipse-based LDAP browser and directory client tool, that also bundles the latest version of the LDAPV3 certified ApacheDS LDAP Server. So playing a bit with it, you can see that creating and launching a new LDAP server is so easy!
Another option if you already have a Synology NAS is to set up and use the Synology LDAP Server for DSM. It is also based on the LDAPV3. Using its GUI allows you to set up your users and groups in minutes.
Intro to LDAP Authentication
We will configure LDAP Authentication in similar ways we’ve seen before, i.e. either using an ldapAuthentication() configurer, via an AuthenticationManagerBuilder in our Custom Security Configuration class, or defining a LdapAuthenticationProvider bean inside the same class.
Again, we will need both: an authenticator that will validate/authenticate the username and τηε password, and the authorities/roles that have been granted to that user.
LDAP Bind Authentication
You will rarely work with LDAP servers allowing you to obtain a user’s password and use it with an encoder/decoder like the Bcrypt we’ve seen in the previous posts. Such behavior is for sure a better security option since passwords are kept within the server. So, here we are going to use the LDAP Bind Authentication. In Bind Operations the LDAP Server does not expose the user’s password, and the LDAP server, on its own, is responsible for password validation/matching with the user password provided.
Spring Security’s LDAP Authentication support does not use the UserDetailsService (as it does with JDBC Authentication) because LDAP bind authentication does not allow clients to read the password or even a hashed version of the password. This means there is no way for a password to be read and authenticated by Spring Security. So, here, we will not use any BCryptPasswordEncoder Bean.
However, Spring uses a similar approach, implementing the LdapAuthoritiesPopulator interface via the LdapAuthenticator interface.
Population of LDAP authorities/roles
The LdapAuthoritiesPopulator implementation is used by the LdapAuthenticationProvider once a user has been authenticated, to create the final user details object. Actually, the LdapAuthoritiesPopulator is responsible to return the authorities/roles granted to that user.
Of course, we can obtain those authorities or roles from the LDAP server, however, only if the LDAP server has been correctly set up to provide them. Note, that an organization can maintain dozens if not hundreds of applications and that the authorizations (authorities or roles) are varying from application to application, for a given user. Furthermore, they can keep changing very frequently. This can be really painful for managing the LDAP server and keeping it up today with all those applications users and changes.
Another approach is to assign to an LDAP Server the centralized authentication of users and maintain users’ authorizations in the application database. Moreover, this approach offers some other benefits such as the registration of external users of that particular application that can be maintained locally, in the application database, as well as users’ authorities/roles.
Implementation
So, what we actually will do, is show how you can leverage LdapAuthoritiesPopulator with the custom UserDetailsService class, which we have seen in the previous post.
Implementation Prerequisites
Dependencies
There is a number of available dependencies enabling us to work with an LDAP server. However, here we are not going to go in detail about LDAP objects and/or their management (add, change LDAP entries, etc.). Similarly, we are not going to use any embedded LDAP server for testing purposes. It is presumed that you have gained some familiarity with LDAP concepts and that you already have access to an LDAP Server. If you haven’t, you might think it as a good chance to get access to the official Spring documentation, e.g. here, here, and here and moreover to install the Apache Directory Studio and create/use an instance of the ApacheDS LDAP Server.
After that, the following 2 dependencies you see below in the project’s Maven pom.xml, are enough for our purposes:
Connection settings in application.properties file
We can use the LDAP connection parameters, using literal strings inside our app, however putting them in the application.properties file allows us full flexibility. Below is just an example of such parameters and you might need to experiment a bit to adjust them for your own settings.
What we need is the access URL of the LDAP Server (the default port is 389 for the LDAP protocol). This is what the 1st line defines. Moreover, in order to use the Bind Authentication we need to have a managerial role defined for us in the LDAP server, which will allow us to search for an object, eg. for a user. Lines 2 and 3 define the search base to do this. Finally, in lines 4 and 5 we provide the manager’s credentials.
Those who have some more experience working with LDAP objects can catch that the trick here is that we are using a filter to search for the user in the LDAP directory. The filter itself uses the default LDAP objects (objectClass and inetOrgPerson) and it is most likely that it will work in most simple cases. The uid={0} is the placeholder for the username provided in user credentials in the Basic Authentication header of the request.
Custom implementation of an LdapAuthoritiesPopulator class
Now, we can proceed further with our code.
Implementing a custom LdapAuthoritiesPopulator class is actually the customization of the getGrantedAuthorities() method of the Spring LdapAuthoritiesPopulator interface.
The implementation of the getGrantedAuthorities() method needs 2 parameters. Both, the userData object and the username are passed in automatically by the Spring mechanism (via the implementation of the DirContextOperations Interface – DirContextAdapter class coming out from the LDAP authenticator) after the user has been authenticated.
The implementation of getGrantedAuthorities() method should return the list of authorities granted to the authenticated user. The list is defined as a Collection of GrantedAuthority objects. As the official documentation states: “A GrantedAuthority must either represent itself as a String or be specifically supported by an AccessDecisionManager.“, we can implement the getGrantedAuthorities() method in a way to return an ArrayList of Strings. Below is how we can do that:
What we actually do, is nothing more than re-use our custom UserDetailsService, i.e. the service class CustomUserDetailsService. Since this class implements the UserDetailsService interface, it also implements the method loadUserByUsername(). The loadUserByUsername() method in its turn, returns a UserDetails object. And finally, we obtain the user’s authorities by accessing the getAuthorities() method.
Of course, our custom UserDetailsService implementation uses again our UsersRepo and this is that does the real job, by obtaining a user’s object with the user’s authorities (roles) calling the repo’s method findByName(). This actually takes place via the JDBCTemplate that communicates with our MariaDB/MySQL. However, we have to stress here, that the passwords that are stored in the ‘password’ column in the ‘users’ table in MariaDB/MySQL schema have nothing to do with the passwords inside the LDAP server. Only the username should be the same.
So, implementing a custom LdapAuthoritiesPopulator is quite easy, since we re-use our already existing class UserService (and UsersRepo). As we’ve said, this way, we can maintain our users’ authoriazations and roles separately from the LDAP Server, and furthermore, we can also provide some pre-defined authorities/roles e.g. for users that are not found in our UsersRepo (external users for instance).
Next, we are going to use our custom implementation of LdapAuthoritiesPopulator (.i.e. our class LdapUserAuthoritiesPopulator) with both: an LdapAuthenticationProviderConfigurer and an LdapAuthenticationProvider.
Using an LdapAuthenticationProviderConfigurer via the AuthenticationManagerBuilder
Similarly with what we’ve previously done implementing jdbcAuthentication(), here we are going to implement the ldapAuthentication(). The ldapAuthentication() is also a method provided by the Spring Security class AuthenticationManagerBuilder. The method returns an LdapAuthenticationProviderConfigurer that allows us to configure/customise the LDAP authentication.
The whole Security configuration class becomes:
The .ldapAuthentication().contextSource() allows easily configuring a BaseLdapPathContextSource. It returns a LdapAuthenticationProviderConfigurer.ContextSourceBuilder for further customizations. What actually needs configiuration are the LDAP Server URL (.url) as well as the credentials (.managerDn and .managerPassword) of an LDAP user with administration privileges (e.g. the LDAP Server administrator). All of them can be obtained from the application.properties file settings via an Environment object.
After that, we have to add the configuration for searching and validating the user (the principal) that should be authenticated and finally we assign to him/her the authorities using our custom UserAuthoritiesPopulator (we’ve previously seen).
Here, for searching purposes, we use a userSearchFilter, also obtained from the environment variable in the application.properties file.
Find here the repo with the code so far.
Using beans of an LdapAuthenticationProvider with a BindAuthenticator
Again, here we will use n authentication provider Bean, but this time it will be an LdapAuthenticationProvider. Moreover, since we are going to use Bind Authentication, we have to configure our LdapAuthenticationProvider accordingly. This can be done by configuring and using another Bean, a Bind authenticator bean. The authenticator bean, as well as the custom UserAuthoritiesPopulator (we’ve previously seen) are passed as parameters to our LdapAuthenticationProvider bean. Finally, we have also to configure the Spring security context, i.e. the DefaultSpringSecurityContextSource for accessing our preferred LDAP server.
This actually, is almost the same as what we have previously done implementing the jdbcAuthentication() with DaoAuthenticationProvider Bean.
Thus, the whole Custom Security configuration class becomes:
Probably, you can understand the fact that the logic is pretty similar to the jdbc case, we used the configurer. But this time, implementing the “separation of concerns” principle offered by Java OOP and the Spring Framework, made our code a bit tidier and cleaner. And it is for sure the preferable approach for more complex projects.
Find the final code in the repo here.
Conclusion – Takeaways
One thing is important to keep in mind. Whatever authentication mechanism we use, we have always to have an Authentication object available in our custom Security Configuration class, providing authenticated user’s (principal’s) details. The schema below depicts how the authentication mechanism works, and what actually we have seen so far (including this post as well as the previous 2 posts.
That’s it so far! I hope you enjoyed it!
Thank you for reading!
This post is very helpful. Thank you so much
Thank you Abi!