Posted In: Spring, Spring Boot, Spring Security

Example – Spring Boot – Security – Integrating With LDAP – SHA Password

 

Example shows how to implement login/logout using LDAP and Spring Boot. Password is encrypted SHA password. It will be authenticated using LdapShaPasswordEncoder

 

 

1. Create Spring boot project. Refer create-eclipse-spring-boot-application-step-by-step

 
 

2. Choose LDAP checkbox or add following maven entry

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

 
 

3a. Add unboundid maven entry for testing with in memory LDAP. Example is using unboundid

<dependency>
	<groupId>com.unboundid</groupId>
	<artifactId>unboundid-ldapsdk</artifactId>
</dependency>

 
 

3b. Add entry to application.properties

spring.ldap.embedded.base-dn: dc=javausecase,dc=com
spring.ldap.embedded.port=8389
logging.level.org.springframework.web=DEBUG
spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp

 
 

3c. In case you do not want to use in-memory LDAP, for your own LDAP server add entry to application.properties

spring.ldap.urls=ldap://localhost:1235
spring.ldap.username=admin
spring.ldap.password=secret

 
 

4. Load test data in LDAP. Create schema.ldif file under CLASSPATH

dn: dc=javausecase,dc=com
objectclass: top
objectclass: domain
objectclass: extensibleObject
dc: javausecase

dn: ou=groups,dc=javausecase,dc=com
objectclass: top
objectclass: organizationalUnit
ou: groups

dn: ou=subgroups,ou=groups,dc=javausecase,dc=com
objectclass: top
objectclass: organizationalUnit
ou: subgroups

dn: ou=people,dc=javausecase,dc=com
objectclass: top
objectclass: organizationalUnit
ou: people

dn: uid=abhijit,ou=people,dc=javausecase,dc=com
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Abhijit Pednekar
sn: Abhijit
uid: abhijit
userPassword: {SHA}fDYHuOYbzxlE6ehQOmYPIfS28/E=

 
 

5. Use WebSecurityConfigurerAdapter and LdapShaPasswordEncoder to authenticate user

package com.example.springapp;

import java.util.Arrays;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.encoding.LdapShaPasswordEncoder;
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.WebSecurityConfigurerAdapter;
import org.springframework.security.ldap.DefaultSpringSecurityContextSource;

@Configuration
public class WebSecurityConfig
        extends WebSecurityConfigurerAdapter {

	@Override
	protected void configure(HttpSecurity http)
	        throws Exception {
		System.out.println(
		        "WebSecurityConfig configure(HttpSecurity http)");
		http.authorizeRequests().anyRequest()
		        .fullyAuthenticated().and().formLogin();
	}

	@Override
	public void configure(AuthenticationManagerBuilder auth)
	        throws Exception {
		System.out.println(
		        "WebSecurityConfig configure(AuthenticationManagerBuilder auth)");
		auth.ldapAuthentication()
		        .userDnPatterns("uid={0},ou=people")
		        .groupSearchBase("ou=groups")
		        .contextSource(contextSource()).passwordCompare()
		        .passwordEncoder(new LdapShaPasswordEncoder())
		        .passwordAttribute("userPassword");
	}

	@Bean
	public DefaultSpringSecurityContextSource contextSource() {
		System.out.println(
		        "DefaultSpringSecurityContextSource contextSource()");
		return new DefaultSpringSecurityContextSource(
		        Arrays.asList("ldap://localhost:8389/"),
		        "dc=javausecase,dc=com");
	}

}

 
 

6. Create simple login FORM

Login form is automatically generated by Spring Boot. It will also generate hidden CSRF token by default.
 
 

7. Create Controller

package com.example.springapp;

import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class LoginService {
	@RequestMapping("/")
	public String index(Map<String, Object> model) {
		System.out.println("index() called");
		model = new HashMap<String, Object>();
		return "loginSuccess";
	}

	@RequestMapping(value = "/logout",
	        method = RequestMethod.GET)
	public String logout(HttpServletRequest request,
	        HttpServletResponse response) {
		System.out.println("logout() called");
		Authentication auth = SecurityContextHolder.getContext()
		        .getAuthentication();
		if (auth != null) {
			new SecurityContextLogoutHandler().logout(request,
			        response, auth);
		}
		return "redirect:/login";
	}
}

 
 

8. Test

 

8a. Test in browser

 

Use user id abhijit and password abcd123 for testing

 

 

 
 

8b. Test with MockMVC

 

@Test
public void testLogin() throws Exception {

	mvc.perform(MockMvcRequestBuilders.post("/login")
			.accept(MediaType.TEXT_HTML)
			.contentType(
					MediaType.APPLICATION_FORM_URLENCODED)
			.param("username", "abhijit")
			.param("password", "abcd123").with(csrf()))
			.andExpect(status().is3xxRedirection());
}

 
 

8c. Test with Rest template

 
As this example is using CSRF token it is little bit tricky to test it through Rest Template. What I have done here is first call /login and parse HTML to get CSRF token. Use this token to pass on to next call.

 

package com.example.springapp;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.junit.Test;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.client.RestTemplate;

public class Example201723ApplicationTests2 {
	@Test
	public void testBasicAuth2() throws Exception {
		RestTemplate rest = new RestTemplate(
		        new HttpComponentsClientHttpRequestFactory());
		HttpHeaders headers = new HttpHeaders();
		HttpEntity<String> entity = null;
		ResponseEntity<String> response = null;
		String url = "http://localhost:8080/login";
		String csrfToken = "";
		System.out.println("1---------------------");
		try {
			rest.getMessageConverters()
			        .add(new StringHttpMessageConverter());
			headers.setContentType(
			        MediaType.APPLICATION_FORM_URLENCODED);

			entity = new HttpEntity<String>("", headers);
			response = rest.exchange(url, HttpMethod.GET, entity,
			        String.class);
			System.out.println(response.getBody());
			Document doc = Jsoup.parse(response.getBody());
			Elements inputs = doc.getElementsByTag("input");
			for (Element input : inputs) {
				if (input.toString().contains("_csrf")) {
					csrfToken = input.val();
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}

		System.out.println("2---------------------");
		try {
			rest.getMessageConverters()
			        .add(new StringHttpMessageConverter());
			headers.setContentType(
			        MediaType.APPLICATION_FORM_URLENCODED);

			entity = new HttpEntity<String>(
			        "username=abhijit&password=abcd123&_csrf="
			                + csrfToken,
			        headers);
			response = rest.exchange(url, HttpMethod.POST,
			        entity, String.class);
			System.out.println(response.getBody());
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

 
 

 
 

Tags: , , , , , ,

by , on February 19th, 2017

  • Categories