Securing Spring Boot API With API Key and Secret

In Web applications, securing the APIs is critical. One of the common methods of securing the APIs is by using API keys and secrets. This ensures that only the authorized clients can access the API endpoints. This article can guide you through the process of securing the Spring Boot API using the API keys and secrets.

Securing the Spring Boot API with an API key and Secret can involve authenticating and authorizing the incoming requests to ensure only valid clients can access the API endpoints. This concept primarily revolves around intercepting HTTP requests and extracting the API key and secret, validating them. It can allow or deny access based on the validity of the application.

1. Authentication and Authorization

  • Authentication is the process of verifying the identity of the client. In this case, it can involve checking the provided API key and secret.
  • Authorization is the process of checking whether the authenticated client has permission to access the specific resource.

2.API Key and Secret

The API Key is a unique identifier used to the authenticate the client making a request. The API Secret is a private key can be used in conjunction with the API Key to the add the extra layer of the security. Both are typically provided in the request headers.

3.Custom Authentication token

To handle the API Key and secret authentication, We need a custom authentication token that can extends the Abstract Authentication Token. This token will hold the API Key and secret and manage the authentication state of the application.

4.Custom Authentication Filter

The Custom filter can be need to intercept the HTTP requests and extract the API key and secret from the header. This filter will attempt to the authenticate the request by the creating an instance of API Key Authentication Token and passing it to the authentication manager.

5.Security Configuration

The Spring Security configuration sets up the custom authentication filter and defines the security rules for the API endpoints. The filter can added to the security filter chain to the process requests to /api/** endpoints.

6.Endpoint Implementation

Finally, we can develop the API endpoint is defined in the controller that can demonstrates the secured endpoint.

Implementation to Secure Spring Boot API With API Key and Secret

We can develop the simple spring boot application that can demonstrates the securing spring boot API key and secret of the application.

Step 1: Create the Spring project.

Create a new Spring Boot project using Spring Initializr and add the required dependencies,

  • Spring Web
  • Spring Security
  • Lombok
  • Spring DevTools

After the creation of the project has done, the folder structure will be like below image.


Step 2: Configure the Application properties

Open application.properties file and add the configuration for the server port in the project.

spring.application.name=spring-boot-secure-API
server.port=8081


Step 3: Create the ApiKeyAuthenticationToken class

This class can handles the API Key and Secret authentication and this token will holds the API Key and secret and manage the authentication state.

Go to src > main > java > org.example.springbootsecureapi > config > ApiKeyAuthenticationToken and put the below code.

Java
package org.example.springbootsecureapi.config;


import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;

import java.util.Collection;



public class ApiKeyAuthenticationToken extends AbstractAuthenticationToken {

    private final String apiKey;
    private final String apiSecret;

    public ApiKeyAuthenticationToken(String apiKey, String apiSecret) {
        super(null);
        this.apiKey = apiKey;
        this.apiSecret = apiSecret;
        setAuthenticated(false);
    }

    public ApiKeyAuthenticationToken(String apiKey, String apiSecret, Collection<? extends GrantedAuthority> authorities) {
        super(authorities);
        this.apiKey = apiKey;
        this.apiSecret = apiSecret;
        super.setAuthenticated(true);
    }

    @Override
    public Object getCredentials() {
        return this.apiSecret;
    }

    @Override
    public Object getPrincipal() {
        return this.apiKey;
    }

    @Override
    public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
        if (isAuthenticated) {
            throw new IllegalArgumentException("Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
        }
        super.setAuthenticated(false);
    }

    @Override
    public void eraseCredentials() {
        super.eraseCredentials();
    }
}


Step 4: Create the ApiKeyAuthFilter class

This filter class will attempt to the authenticate the request by creating an instance of ApiKeyAuthenticationToken and passing it to authentication manager.

Go to src > main > java > org.example.springbootsecureapi > config > ApiKeyAuthFilter and put the below code.

Java
package org.example.springbootsecureapi.config;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.RequestMatcher;

import java.io.IOException;



public class ApiKeyAuthFilter extends AbstractAuthenticationProcessingFilter {

    private static final String API_KEY_HEADER = "API-Key";
    private static final String API_SECRET_HEADER = "API-Secret";

    public ApiKeyAuthFilter(RequestMatcher requiresAuth) {
        super(requiresAuth);
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
            throws AuthenticationException, IOException {
        String apiKey = request.getHeader(API_KEY_HEADER);
        String apiSecret = request.getHeader(API_SECRET_HEADER);

        if (apiKey == null || apiSecret == null) {
            throw new RuntimeException("Missing API Key or Secret");
        }

        Authentication auth = new ApiKeyAuthenticationToken(apiKey, apiSecret);
        return getAuthenticationManager().authenticate(auth);
    }

    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        super.successfulAuthentication(request, response, chain, authResult);
        chain.doFilter(request, response);
    }
}


Step 5: Create the SecurityConfig class

This SecurityConfig class can sets up the custom authentication filter and defines the security rules for the API endpoints.

Go to src > main > java > org.example.springbootsecureapi > config > SecurityConfig and put the below code.

Java
package org.example.springbootsecureapi.config;



import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;



import java.util.Collections;



import java.util.Collections;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        ApiKeyAuthFilter filter = new ApiKeyAuthFilter(new AntPathRequestMatcher("/api/**"));
        filter.setAuthenticationManager(authentication -> {
            String apiKey = (String) authentication.getPrincipal();
            String apiSecret = (String) authentication.getCredentials();

            if ("valid-api-key".equals(apiKey) && "valid-api-secret".equals(apiSecret)) {
                return new ApiKeyAuthenticationToken(apiKey, apiSecret, Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER")));
            } else {
                authentication.setAuthenticated(false);
            }

            return authentication;
        });

        http
                .csrf().disable()
                .authorizeRequests(authorizeRequests ->
                        authorizeRequests
                                .requestMatchers("/api/**").authenticated()
                                .anyRequest().permitAll()
                )
                .addFilterBefore(filter, UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }
}


Step 6: Create the ApiController class

Go to src > main > java > org.example.springbootsecureapi > controller > ApiController and put the below code.

Java
package org.example.springbootsecureapi.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api")
public class ApiController {

    @GetMapping("/data")
    public String getSecureData() {
        return "This is Spring Boot API secured data!";
    }
}


Step 7: Main Class

Main class will be as it is, we do not need to change anything.

Java
package org.example.springbootsecureapi;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringBootSecureApiApplication {

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

}


pom.xml:

XML
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>org.example</groupId>
    <artifactId>spring-boot-secure-API</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-boot-secure-API</name>
    <description>spring-boot-secure-API</description>
    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>


Step 8: Run the application

Once, we run the application, it will start at port 8081.

Step 9: Endpoint Testing

1. Endpoint without the api key and secret

GET http://localhost:8081/api/data

Then show the error like below:

Missing API Key and secret

Output:


2. Endpoint Test with API key and secret

GET http://localhost:8081/api/data

Add the API key and secret in Header section.

API Key : valid-api-key
API Secret: valid-api-key

Output:


By the following these steps, we can secure the Spring Boot API using API keys and secrets. This method ensures that only the clients with valid credentials can access the API endpoints and thereby adding the extra layer of the security to the Spring Boot application.



Contact Us