RESTful API Security & LDAP Authentication with Spring

Blog

January 5, 2018

TABLE OF CONTENTS

Introduction

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:

  • A limited number of APIs are being exposed
  • There is a very small audience of service consumers, and/or
  • There are few or no API management requirements

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.

arrow-right

Our 2020 Legacy Modernization Report is here.

We surveyed hundreds of IT executives to understand their biggest challenges. See the survey results, symptoms of legacy systems, and our business solutions on modernization to improve business success.

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:

  • Presents a simple login form if used to secure a web page
  • Disables cross-site forgery
  • antMatchers will match one or more endpoints
  • Enables HTTP Basic Authentication
  • Enables STATELESS session; every call to the RESTful API will require authentication

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

  • Connects to the LDAP resource
  • Authenticates using the username & pwd fields from the HEADER; or simple login form

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:

  • spring-security-core
  • spring-security-we
  • spring-security-config
  • spring-security-ldap
  • commons-logging
  • slf4j-log4j12
  • apacheds-server-jndi
  • slf4j-api
  • javax.servlet-api

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:

  • Add it as a Maven dependency, or
  • Add the jar to your project
  • Add file application.properties where Spring can locate it

Voila. Endpoint(s) secured.

Authored By

Shoulico Freeman

Shoulico Freeman

Meet our Experts

Shoulico Freeman

Shoulico Freeman

Let's chat.

You're doing big things, and big things come with big challenges. We're here to help.

Read the Blog

By clicking the button below you agree to our Terms of Service and Privacy Policy.

levvel mark white

Let's improve the world together.

© Levvel & Endava 2024