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
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 supportBCrypt 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
Authorize API
Click on the Authorize button, enter the credentials user / password and click on Authorize button as shown below
Test the Services
Create Product
Click on the Create Product endpoint and click on “Try it out” and then execute
Request
Response
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
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