Levvel Blog - RESTful API Security & LDAP Authentication with Spring

RESTful API Security & LDAP Authentication with Spring

In past posts, we’ve discussed API security within the context of a large enterprise who expose many endpoints, have a diverse and sizeable population of service consumers, and need comprehensive API management capabilities. This sometimes necessitates a more sophisticated API management solution.

A lightweight solution may be better suited to the task in certain situations, however, such as:

This post discusses one such solution: creating a reusable REST API security Java component that authenticates users via LDAP for applications not using Spring’s Security.

For demonstration simplicity and clarity, HTTP Basic Authentication is used. In practice, this is almost never a good idea. Since HTTP Basic Authentication protocol is being used, you should make sure the endpoints are accessed via HTTPS; otherwise the cleartext credentials can be hacked.

Security Web Application Initializer Class

Because the current RESTful API is not utilizing Spring, this class is responsible for:

  1. Adding a ContextLoadListener to bootstrap SecurityConfig
  2. Registering the SpringSecurityFilterChain for every route in the RESTful API

If your API is using Spring Security, wire SecurityConfig in with the rest of your Spring beans and do not implement this class.

import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;

public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer {

    public SecurityWebApplicationInitializer() {
        super(SecurityConfig.class);
    }
}

Security Configuration Class


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
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;

/**
 * Security Configuration - LDAP and HTTP Basic Authorizations.
 */
@Configuration
@PropertySource("classpath:application.properties")
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    private static final Logger LOG = LoggerFactory.getLogger(SecurityConfig.class);

    @Value("${secure-end-points}") private String secureEndPoints;
    @Value("${user-search-filter}") private String userSearchFilter;
    @Value("${user-dn-patterns}") private String userDnPatterns;
    @Value("${ldap-url}") private String url;
    @Value("${ldap-port}") private String port;
    @Value("${ldap-context-root}") private String contextRoot;
    @Value("${manager-dn}") private String managerDn;
    @Value("${manager-password}") private String managerPassword;

    /**
     * Default constructor
     */
    public SecurityConfig() {
        super();
    }

    @Override protected void configure(HttpSecurity http) throws Exception {
        final String[] endPointsArray = this.secureEndPoints.split(",");
        http.csrf()
                .disable()
                .requestMatchers()
                .antMatchers(endPointsArray)
                .and()
                .authorizeRequests()
                .antMatchers(endPointsArray)
                .authenticated()
                .and()
                .httpBasic()
                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }

    @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        try {
            String completeUrl = new StringBuffer(this.url).append(":")
                    .append(this.port)
                    .append("/")
                    .append(this.contextRoot)
                    .toString();

            auth.ldapAuthentication()
                    .userSearchFilter(userSearchFilter)
                    .userDnPatterns(userDnPatterns)
                    .contextSource()
                    .url(completeUrl)
                    .managerDn(managerDn)
                    .managerPassword(managerPassword);
        }
        catch (Exception ex) {
           LOG.error("Handle exception here");
            throw ex;
        }
    }

    /**
     * In order to resolve ${...} placeholders in definitions or @Value annotations using properties
     * from a PropertySource, one must register a PropertySourcesPlaceholderConfigurer. This happens
     * automatically when using XML configuration, but must be explicitly registered using a static
     * @Bean method when using @Configuration classes.
     */
    @Bean public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }
}

Method “configure(HttpSecurity http)” does the following:

Method “configure(AuthenticationManagerBuilder auth)” does the following:

Note: The complete LDAP URL was built, line 59 of class SecurityConfig, because at the time of this writing, Spring’s API methods, port() & some others, were not working.

Dependencies

Here are the dependencies required for security component:

https://gist.github.com/Shallako/c0322971d5d6d626aa9b0f0f82dff6b3

Application Property

This blog assumes that the reader is already familiar with Spring LDAP authentication and how to configure it, if not, please visit https://spring.io/guides/gs/authenticating-ldap/ for a comprehensive tutorial.

Configuration file example:

# Spring will load properties from the following locations:
#
# 1.) A "/config" subdirectory of the current directory.
# 2.) The current directory
# 3.) A classpath "/config"
# 4.) The classpath root

# Or pass them on the command line to override property file.
# -Dldap-port=9989 -Dsecure-end-points=/a/c/** etc.

# comma seperated values of endpoints to secure multiple endpoints
secure-end-points=/endpoint1/**,/endpoint2/user/**

#LDAP properties
ldap-url=ldap://url
ldap-port=2389
ldap-context-root=ou=levvelusers,dc=dsu,dc=abc,dc=123
user-search-filter=(mail={0})
manager-dn=uid=appadmin,ou=levvlaccounts,cn=config
manager-password=pwd
user-dn-pattern

Line 12 of the configuration example demonstrates the format used when securing the URL endpoints. Wildcard usage is allowed and helps keep the endpoint configuration concise. In our example, all services under “endpoint1” are secured, while only services under the “user” branch of “endpoint2” need to be secured.

Integration How-To

Securing existing RESTful APIs with this module is extremely simple. Just add the following to the war project of your application:

Voila. Endpoint(s) secured.

Shoulico Freeman

Shoulico Freeman

Senior Consultant

Shoulico Freeman has been a Spring Framework developer since 2004 when it was in beta. In 2004, he helped convince a telecommunications company of the importance of utilizing Spring Framework in its Java EE/BEA Weblogic project. Since then, he has utilized and presented several of Spring Framework’s projects.

Related Posts