Secure Spring Boot REST API using Basic Authentication

This is the third post of my Spring Boot Blog post series. In the very first post, I talked about my experience with creating RESTFul Services using Spring Boot. Then I have expanded the sample to integrate with Swagger documentation. In this post, I am going to expand above sample with security aspect.

What is API Security

API Security is a wide area with many different definitions, meanings, and solutions. The main key terms in API security are Authorization, Authentication, Encryption, Federation, and Delegation. However, I am not going to talk about each of them here.

What is Authentication

Authentication is used to reliably determine the identity of an end user and give access to the resources based on the correctly identified user.

What is Basic Authentication

Basic Authentication is the simplest way to enforce access controling to resources. Here, the HTTP user agent provides the username and the password when making a request. The string containing the username and password separated by a colon is Base64 encoded before sending to the backend when authentication is required.

How to Invoke Basic Auth Protected API

Option 1: Send Authorization header. This value is base64 encoded username:password

Ex: "Authorization: Basic Y2hhbmRhbmE6Y2hhbmRhbmE="

curl -X GET http://localhost:8080/admin/hello/chandana -H 'authorization: Basic Y2hhbmRhbmE6Y2hhbmRhbmE='

Option 2: Using URL:

curl -X GET -u username:password  http://localhost:8080/admin/hello/chandana


OK, we talked about basic stuff. So let's move to see how to secure a REST API using Spring Security. You can download the initial sample code from my GitHub repo(Swagger Spring Boot Project source code)

To enhance our previous sample with basic auth security, first I am going to add "spring-boot-starter-security" and "spring-boot-starter-tomcat" dependencies into the pom file.

        <!-- -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
        </dependency>

Next step is that our configuration class is annotated with @EnableWebSecurity annotation and configuration class is extended from the WebSecurityConfigurerAdapter. The EnableWebSecurity annotation will enable Spring-Security web security support.

@Configuration
@EnableSwagger2
@EnableWebSecurity
public class ApplicationConfig extends WebSecurityConfigurerAdapter {

Overridden configure(HttpSecurity) method is used to define which URL paths should be secured and which should not be. In my example "/" and "/api" paths are not required any authentication and any other paths(ex:  "admin") should be authenticated with basic auth.

@Override
protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
        http.authorizeRequests().antMatchers("/", "/api/**").permitAll()
        .anyRequest().authenticated();
        http.httpBasic().authenticationEntryPoint(basicAuthenticationPoint);
}

In the configureGlobal(AuthenticationManagerBuilder) method, I have created an in-memory user store with a user called 'chandana'. There I have added username, password, and userole for the in-memory user.

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

In Addition to that, you can see that I have added autowired BasicAuthenticationPoint, into my config class. Purpose of the BasicAuthenticationEntryPoint class is to set the "WWW-Authenticate" header to the response. So, web browsers will display a dialog to enter usename and password based on basic authentication mechanism(WWW-Authenticate header)

Then you can run the sample using "mvn spring-boot:run". When you are accessing "localhost:8080/api/hello/chandana" basic authentication is not required to invoke the api. However, if you try to access the "localhost:8080/admin/hello/chandana" it will be required to provide basic auth credentials to access the resource.

AppConfig class:

 package com.chandana.helloworld.config;  
 import org.springframework.beans.factory.annotation.Autowired;  
 import org.springframework.context.annotation.Bean;  
 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.configuration.EnableWebSecurity;  
 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;  
 import springfox.documentation.builders.ApiInfoBuilder;  
 import springfox.documentation.builders.PathSelectors;  
 import springfox.documentation.builders.RequestHandlerSelectors;  
 import springfox.documentation.service.ApiInfo;  
 import springfox.documentation.service.Contact;  
 import springfox.documentation.spi.DocumentationType;  
 import springfox.documentation.spring.web.plugins.Docket;  
 import springfox.documentation.swagger2.annotations.EnableSwagger2;  
 @Configuration  
 @EnableSwagger2  
 @EnableWebSecurity  
 public class ApplicationConfig extends WebSecurityConfigurerAdapter {  
   @Autowired  
   private BasicAuthenticationPoint basicAuthenticationPoint;  
   @Bean  
   public Docket api() {  
     return new Docket(DocumentationType.SWAGGER_2)  
         .apiInfo(getApiInfo())  
         .select()  
         .apis(RequestHandlerSelectors.basePackage("com.chandana.helloworld.controllers"))  
         .paths(PathSelectors.any())  
         .build();  
   }  
   @Override  
   protected void configure(HttpSecurity http) throws Exception {  
     http.csrf().disable();  
     http.authorizeRequests().antMatchers("/", "/api/**").permitAll()  
     .anyRequest().authenticated();  
     http.httpBasic().authenticationEntryPoint(basicAuthenticationPoint);  
   }  
   private ApiInfo getApiInfo() {  
     Contact contact = new Contact("Chandana Napagoda", "http://blog.napagoda.com", "cnapagoda@gmail.com");  
     return new ApiInfoBuilder()  
         .title("Example Api Title")  
         .description("Example Api Definition")  
         .version("1.0.0")  
         .license("Apache 2.0")  
         .licenseUrl("http://www.apache.org/licenses/LICENSE-2.0")  
         .contact(contact)  
         .build();  
   }  
   @Autowired  
   public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {  
     auth.inMemoryAuthentication().withUser("chandana").password("chandana").roles("USER");  
   }  
 }  

BasicAuthenticationEntryPoint  class:

 package com.chandana.helloworld.config;  
 import org.springframework.security.core.AuthenticationException;  
 import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;  
 import org.springframework.stereotype.Component;  
 import java.io.IOException;  
 import java.io.PrintWriter;  
 import javax.servlet.ServletException;  
 import javax.servlet.http.HttpServletRequest;  
 import javax.servlet.http.HttpServletResponse;  
 @Component  
 public class BasicAuthenticationPoint 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("Chandana");  
     super.afterPropertiesSet();  
   }  
 }  

You can download Spring Boot Basic Auth Project source code from my GitHub repo as well.

Post a Comment

Popular posts from this blog

Yield Price Sri Lanka - Android Application

Manage SOAPAction of the Out Message

How to clean Registry log (REG_LOG) table