3 Steps to Secure Spring Boot 2 REST API using LDAP Authentication and Authorization with MySQL Database

In the previous article we have secured the REST API with Spring Security JWT Authentication. Now we are gonna add LDAP Authentication and Role Based Authorization with Database to the same REST API that we have implemented previouly using Spring Security 5.

Introduction to LDAP

LDAP (Lightweight Directory Access Protocol) is a protocol that runs on TCP/IP. It is used to access directory services like Microsoft’s Active Directory, OpenLDAP, OpenDJ, Sun ONE Directory Server or ApacheDS. A directory service is a kind of database or data store, but not necessarily a relational database.

LDAP node is created with the following keywords.
ou : Organizational unit
cn : Common Name
sn : Surname
uid : User Id
dn : Distinguished name
dc : Domain Component

The full DN of the user will be used to find the user in LDAP server. For example, if the username is ‘tester’ then the actual name used to authenticate to LDAP will be the full DN as following.

uid=tester,ou=users,dc=javachinna,dc=com

LDAP Server Setup

We will be using Apache Directory Studio as the LDAP server and browser for this article. If you wanna setup ApacheDS, then you can refer the article here. You don’t need this server if you are already having a Directory Server.

After server setup, the Directory Information Tree (DIT) of the server is as shown below

ApacheDS DIT

Let’s Get Started

We will be configuring Spring Security for performing 2 operations:

  • Authenticating User – Configure Spring Security to authenticate with LDAP server
  • Authorizing User– If the authentication is successful, then find the user by username in the database and fetch the user roles required for authorization.

Step 1: Add LDAP dependencies

pom.xml

		<dependency>
            <groupId>org.springframework.ldap</groupId>
            <artifactId>spring-ldap-core</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-ldap</artifactId>
        </dependency>
        <dependency>
            <groupId>com.unboundid</groupId>
            <artifactId>unboundid-ldapsdk</artifactId>
        </dependency>

Step 2: Implement Authorities Populator

LdapUserAuthoritiesPopulator.java

LdapUserAuthoritiesPopulator implements LdapAuthoritiesPopulator interface to fetch the user roles by user name from the database.

@Slf4j annotation causes lombok to generate a logger field

package com.javachinna.config;

import java.util.Collection;
import java.util.HashSet;

import org.springframework.ldap.core.DirContextOperations;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator;
import org.springframework.stereotype.Component;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@RequiredArgsConstructor
@Component
public class LdapUserAuthoritiesPopulator implements LdapAuthoritiesPopulator {

	private final UserDetailsService userDetailsService;

	@Override
	public Collection<? extends GrantedAuthority> getGrantedAuthorities(DirContextOperations userData, String username) {
		Collection<? extends GrantedAuthority> authorities = new HashSet<>();
		try {
			authorities = userDetailsService.loadUserByUsername(username).getAuthorities();
		} catch (Exception e) {
			log.error("Exception occurred while trying to fetch the user authorities from the database");
		}
		return authorities;
	}
}

Note: if the given user name not found in the database, then an empty list will be returned. In this case we will get 403 Forbidden error in the response.

Step 3: Configure Spring Security LDAP Authentication

Profiles.java

Create a constant for LDAP Authentication profile

package com.javachinna.config;

public class Profiles {

	private Profiles() {
	}

	public static final String BASIC_AUTH = "basicauth";
	public static final String JWT_AUTH = "jwtauth";
	public static final String LDAP_AUTH = "ldapauth";
}

LdapAuthSecurityConfig.java

@Profile(Profiles.LDAP_AUTH) annotation is used to enable LDAP authentication only when the application is run with “ldapauth” profile.

LdapAuthSecurityConfig class extends the WebSecurityConfigurerAdapter which is a convenience class that allows customization to both WebSecurity and HttpSecurity.

I’ve provided comments for all the configurations below for clarity

package com.javachinna.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.password.LdapShaPasswordEncoder;

import com.javachinna.model.Role;

import lombok.RequiredArgsConstructor;

@Profile(Profiles.LDAP_AUTH)
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class LdapAuthSecurityConfig extends WebSecurityConfigurerAdapter {

	private final LdapUserAuthoritiesPopulator ldapUserAuthoritiesPopulator;

	@Autowired
	public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {

		// Returns LdapAuthenticationProviderConfigurer to allow customization of the
		// LDAP authentication
		auth.ldapAuthentication()
				// Pass the LDAP patterns for finding the username.
				// The key "{0}" will be substituted with the username
				.userDnPatterns("uid={0},ou=users")
				// Pass search base as argument for group membership searches.
				.groupSearchBase("ou=groups")
				// Configures base LDAP path context source
				.contextSource().url("ldap://localhost:10389/dc=javachinna,dc=com")
				// DN of the user who will bind to the LDAP server to perform the search
				.managerDn("uid=admin,ou=system")
				// Password of the user who will bind to the LDAP server to perform the search
				.managerPassword("secret").and()
				// Configures LDAP compare operation of the user password to authenticate
				.passwordCompare().passwordEncoder(new LdapShaPasswordEncoder())
				// Specifies the attribute in the directory which contains the user password.
				// Defaults to "userPassword".
				.passwordAttribute("userPassword").and()
				// Populates the user roles by LDAP user name from database
				.ldapAuthoritiesPopulator(ldapUserAuthoritiesPopulator);
	}

	@Override
	protected void configure(HttpSecurity httpSecurity) throws Exception {
		// Disable CSRF
		httpSecurity.csrf().disable()
				// Only admin can perform HTTP delete operation
				.authorizeRequests().antMatchers(HttpMethod.DELETE).hasRole(Role.ADMIN)
				// any authenticated user can perform all other operations
				.antMatchers("/products/**").hasAnyRole(Role.ADMIN, Role.USER).and().httpBasic()
				// Permit all other request without authentication
				.and().authorizeRequests().anyRequest().permitAll()
				// We don't need sessions to be created.
				.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
	}
}

Note: If managerDn is not provided, then anonymous access will be used. if anonymous access is disabled in LDAP server, then authentiation will fail.

LdapShaPasswordEncoder is deprecated. However, I’ve used this encoder since ApacheDS doesn’t support BCrypt Encoding

Run with LDAP Auth Profile

You can run the application using mvn spring-boot:run -Dspring-boot.run.profiles=ldapauth and hit the url http://localhost:8080/swagger-ui.html in the browser. All the endpoints will be secured as shown below

REST API LDAP Authentication Security

Authorize API

Click on the Authorize button, enter the credentials user / password and click on Authorize button as shown below

Swagger Authorize Dialog

Test the Services

Create Product

Click on the Create Product endpoint and click on “Try it out” and then execute

Request

Create Product Request

Response

Create Product Response

Delete Product

Request

Delete Product Request

Response

Since the product can be deleted by users with “Admin” role only, we are getting HTTP Status 403 Forbidden in the response

Delete Product Response

Logout and login again with the admin credentials in the Authorize dialog and execute the delete API again. The product will be deleted.

Source Code

As always, you can get the source code from the Github below

https://github.com/JavaChinna/spring-boot-rest-ldap-auth

Conclusion

That’s all folks! In this article, you’ve learned how to implement LDAP authentication and authorization for Spring Boot RESTful services.

I hope you enjoyed this article. Thank you for reading.

Read Next: Disable Spring Security for a Profile in Spring Boot

Leave a Reply