Spring Boot Security + REST + Basic Authentication

Spring Boot Security + REST + Basic Authentication thumbnail
313K
By Dhiraj 07 May, 2018

In the last post we tried securing our Spring MVC app using spring security Spring Boot Security Login Example.We protected our app against CSRF attack too. Today we will see how to secure REST Api using Basic Authentication with Spring security features.Here we will be using Spring boot to avoid basic configurations and complete java config.We will try to perform simple CRUD operation using Spring REST and user requires to provide username and password to access these resources.At the end, we will be testing our app using postman.

What is Basic Authentication

Basic authentification is a standard HTTP header with the user and password encoded in base64 : Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==.The userName and password is encoded in the format username:password. This is one of the simplest technique to protect the REST resources because it does not require cookies. session identifiers or any login pages.

In case of basic authentication, the username and password is only encoded with Base64, but not encrypted or hashed in any way. Hence, it can be compromised by any man in the middle. Hence, it is always recommended to authenticate rest API calls by this header over a ssl connection.

BasicAuthenticationFilter in Spring

BasicAuthenticationFilter in Spring is the class which is responsible for processing basic authentication credentials presented in HTTP Headers and putting the result into the SecurityContextHolder. The standard governing HTTP Basic Authentication is defined by RFC 1945, Section 11, and BasicAuthenticationFilter confirms with this RFC.

Environment Setup

1. JDK 8 2. Spring Boot 3. Intellij Idea/ eclipse 4. Maven

Maven Dependencies

spring-boot-starter-parent: provides useful Maven defaults. It also provides a dependency-management section so that you can omit version tags for existing dependencies.

spring-boot-starter-web: includes all the dependencies required to create a web app. This will avoid lining up different spring common project versions.

spring-boot-starter-tomcat: enable an embedded Apache Tomcat 7 instance, by default. We have overriden this by defining our version. This can be also marked as provided if you wish to deploy the war to any other standalone tomcat.

spring-boot-starter-security: take care of all the required dependencies related to spring security.

pom.xml
    <properties>
        <tomcat.version>8.0.3</tomcat.version>
		<start-class>spring-boot-example.Application</start-class>
    </properties>

	<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.3.3.RELEASE</version>
    </parent>

    <dependencies>
		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
			<!--<scope>provided</scope>-->
        </dependency>
		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
		</dependency>

    </dependencies>

Spring Bean Configuration

SpringBootServletInitializer enables process used in Servlet 3.0 using web.xml

@SpringBootApplication: This is a convenience annotation that is equivalent to declaring @Configuration

@EnableAutoConfiguration and @ComponentScan.

Application.java
package com.developerstack.config;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.web.SpringBootServletInitializer;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@ComponentScan(basePackages = "com.developerstack")
@SpringBootApplication
public class Application extends SpringBootServletInitializer {

	 private static Class applicationClass = Application.class;

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

Now let us define our main configuration for spring security - SpringSecurityConfig.java.class is annotated with @EnableWebSecurity to enable Spring Security web security support. we have extended WebSecurityConfigurerAdapter to override spring features with our custom requirements.Here we want our every request to be authenticated using HTTP Basic authentication. If authentication fails, the configured AuthenticationEntryPoint will be used to retry the authentication process.

SpringSecurityConfig.java
package com.developerstack.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
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.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

@Configuration
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

	@Autowired
	private AuthenticationEntryPoint authEntryPoint;

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.csrf().disable().authorizeRequests()
				.anyRequest().authenticated()
				.and().httpBasic()
				.authenticationEntryPoint(authEntryPoint);
	}

	@Autowired
	public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
		auth.inMemoryAuthentication().withUser("john123").password("password").roles("USER");
	}

}

Now let us define our authentication entry point.This class will be responsible to send response when the credentials are no longer authorized. If the authentication event was successful, or authentication was not attempted because the HTTP header did not contain a supported authentication request, the filter chain will continue as normal. The only time the filter chain will be interrupted is if authentication fails and the AuthenticationEntryPoint is called.

AuthenticationEntryPoint.java
package com.developerstack.config;

import java.io.IOException;
import java.io.PrintWriter;

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

import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
import org.springframework.stereotype.Component;

@Component
public class AuthenticationEntryPoint extends BasicAuthenticationEntryPoint {

	@Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authEx)
      throws IOException, ServletException {
        response.addHeader("WWW-Authenticate", "Basic realm=" +getRealmName());
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        PrintWriter writer = response.getWriter();
        writer.println("HTTP Status 401 - " + authEx.getMessage());
    }

	@Override
    public void afterPropertiesSet() throws Exception {
        setRealmName("DeveloperStack");
        super.afterPropertiesSet();
    }

}
 Other Interesting Posts
AES Encryption and Decryption in Java
Spring Boot JWT Auth
Spring Boot Security OAuth2 Example
Spring Security Password Encoding using Bcrypt Encoder
Spring 5 Features and Enhancements
Spring Boot Security Redirect after Login with complete JavaConfig
Spring Security Hibernate Example with complete JavaConfig
Spring Boot Security Custom Form Login Example
Websocket spring Boot Integration without STOMP
Spring MVC Angularjs Integration with complete JavaConfig
Spring Junit Integration with complete JavaConfig
Spring Ehcache Cacheable Example with complete javaConfig
Spring Boot Spring MVC Example
Spring Boot Thymeleaf Example

Following is the controller class where we have exposed our APIs.

UserController.java
package com.developerstack.controller;

import java.util.Arrays;
import java.util.List;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import com.developerstack.model.User;

@Controller
public class UserController {

	@RequestMapping(path="/user", method = RequestMethod.GET)
	public ResponseEntity>  listUser(){
		return new ResponseEntity>(getUsers(), HttpStatus.OK);
	}

	@RequestMapping(path="/user/{id}", method = RequestMethod.GET)
	public ResponseEntity  listUser(@PathVariable(value = "id") String id){
		return new ResponseEntity(getUsers().stream().filter(user -> user.getId().equals(id)).findFirst().orElse(null), HttpStatus.OK);

	}

	@RequestMapping(path="/user", method = RequestMethod.POST)
	public ResponseEntity  listUser(@RequestBody User user){
		return new ResponseEntity("18", HttpStatus.OK);
	}

Logout Implementation

After a succesdfull authentication, Spring updates the security context with an authentication object that contains credentials, roles, principal etc.So, while logging out we need to clear this context and spring provides SecurityContextLogoutHandler which performs a logout by modifying the SecurityContextHolder.Following is the implementation.

Following logout implementation is one way to logout programatically.You can also configure logout in SpringSecurityConfig.java.You can use any one of them but not both.Once basic authentication is successfull, browser automatically sends basic auth header in every request following successfull authentication.Hence, if you are using this authentication mechanism in a browser based application, it is required to clear the Basic auth cache in the browser too after logout.

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

Handling Cross Origin Requests

To handle Cross Origin requests add following configuration in the bean definition.

@Bean
public WebMvcConfigurer corsConfigurer() {
	return new WebMvcConfigurerAdapter() {
		@Override
		public void addCorsMappings(CorsRegistry registry) {
			registry.addMapping("/**").allowedMethods("GET", "POST", "PUT", "DELETE").allowedOrigins("*")
				.allowedHeaders("*");
		}
	};
}

Run Application

1. Run Application.java as a java application

2. Launch postman

3. Make a get request against http://localhost:8080/user/ with empty authoriztion and a 401 - unauthorised response can be noticed as below

spring-security-rest-authentication-postman-error

4. Now enter username/password as john123/password and make the get request again and you can see the following result.

spring-security-rest-postman-get-request

Spring Boot 2 Basic Authentication

There are certain changes required to run this app with spring boot 2. With spring boot 2, you need to Bcrypt the password.To make use of Bcrypt, first we need to define a Bean of BCryptPasswordEncoder as follow or else it throws error as PasswordEncoder mapped for the id "null"

@Bean
public BCryptPasswordEncoder encoder() {
    return new BCryptPasswordEncoder();
}

Next, we require to Bcrypt our plain-text password in configureGlobal() method. You can use this online Bcrypt tool to generate Bcrypt password.Else, you will be getting this error Encoded password does not look like BCrypt

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    auth.inMemoryAuthentication().withUser("john123").password("$2a$04$AjFEmZeX7mN8zSn57PUEZeJgBeoKMvwteZMBiP57Jb4AGFsUORmLC").roles("USER");
}

Conclusion

I hope this article served you that you were looking for. If you have anything that you want to add or share then please share it below in the comment section.

Download the source

Share

If You Appreciate This, You Can Consider:

We are thankful for your never ending support.

About The Author

author-image
A technology savvy professional with an exceptional capacity to analyze, solve problems and multi-task. Technical expertise in highly scalable distributed systems, self-healing systems, and service-oriented architecture. Technical Skills: Java/J2EE, Spring, Hibernate, Reactive Programming, Microservices, Hystrix, Rest APIs, Java 8, Kafka, Kibana, Elasticsearch, etc.

Further Reading on Spring Security