Secure APIs with Apache APISIX API Gateway
Tags: java, spring, apisix, security
Published: 2022-06-22 05:40:00

## Introduction

In this walkthrough, we are going to look at some of the [Apache APISIX](https://apisix.apache.org/) API Gateway [built-in plugins](https://apisix.apache.org/plugins) for securing your [Spring Boot REST API](https://spring.io/guides/tutorials/rest/)s and demonstrate how to effectively use them. You will learn to run the multi-container app (_backend, db, apisix and etcd_ together) with [Docker compose](https://docs.docker.com/compose/). It contains also conceptual knowledge about [API Security](https://en.wikipedia.org/wiki/Web_API_security), the important role of the API gateway in handling cross-cutting concerns, and how Apache APISIX can help you to simplify and accelerate the task of securing APIs.

### Here, is a short summary of what we do:

✅ Overview of API Security.
✅ The API Gateway as a gatekeeper.
✅ How does an APISIX gateway secure your APIs?
✅ Clone the demo repository apisix-security-java-spring  from GitHub.
✅ Understand the structure of the project and `docker-compose.yaml` file.
✅ Build a multi-container APISIX via Docker CLI.
✅ Allow only whitelisted IP address access your API with [IP restriction plugin](https://apisix.apache.org/docs/apisix/plugins/ip-restriction) in action.
✅ Block direct access to some API resources with [Uri-blocker plugin](https://apisix.apache.org/docs/apisix/plugins/uri-blocker).
✅ Make sure that only allowed users can request your API with [Consumer restriction plugin](https://apisix.apache.org/docs/apisix/plugins/consumer-restriction).


## Key things we need:

👉 [JDK 11+](https://www.oracle.com/java/technologies/downloads/). Make sure that you have OpenJDK package installed in your system. For this demonstration, I used Windows OS and Java 11.
👉 [Docker desktop](https://www.docker.com/products/docker-desktop/) - you need also Docker desktop installed locally to complete this tutorial. It is available for [Windows](https://desktop.docker.com/win/edge/Docker%20Desktop%20Installer.exe) or [macOS](https://desktop.docker.com/mac/edge/Docker.dmg). Or install the [Docker ACI Integration CLI for Linux](https://docs.docker.com/engine/context/aci-integration/#install-the-docker-aci-integration-cli-on-linux). 

💁Before we get started, let's quickly enhance our theoretical knowledge about API security and API Gateway concepts.

## Overview of API Security

**API security** is a term referring to practices and products that prevent malicious attacks on, Application program interfaces (API). Because APIs have become key to programming web-based interactions, they have become a target for hackers. As a result, basic authentication, requiring only user names and passwords, has been replaced with various forms of security methods, such as [multifactor authentication](https://en.wikipedia.org/wiki/Multi-factor_authentication) (MFA) or tokens like [JWT Web Tokens](https://jwt.io/).

![API Security](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6t9jua22cbf9nu16j04p.jpg)
 
In our modern era, API security has become increasingly important. Many hardening techniques are available:

- TLS encryption
- API Firewalls
- Validating request data
- Throttling for protection
- Continuously monitoring
- Auditing
- Logging
- IP restriction
- And more.

**Representational state transfer (REST)** API security is one of the most common API securities available. With REST API security, you have a Hypertext Transfer Protocol (HTTP) Uniform Resource Identifier (URI), which controls which data the API accesses as it operates. REST API security can thus prevent attacks involving malicious data an attacker is trying to introduce using an API.

## An API Gateway as a gatekeeper👮

[An API gateway](https://wikitech.wikimedia.org/wiki/API_Gateway) is an essential component of an [API management](https://en.wikipedia.org/wiki/API_management) solution. It is key to API security and protects the underlying data like a gatekeeper checking authentication and authorization and managing traffic. As a thin layer between your user clients and your upstream services, the API gateway is further extended with countless plugins to handle authentication, authorization, traffic control, and security. 

[Apache APISIX](https://apisix.apache.org/) is a lightweight open-source [API gateway](https://apisix.apache.org/docs/apisix/architecture-design/apisix) that sits in front of your upstream services. Easy to configure and quick to deploy, APISIX API Gateway serves as _a central point_ for routing all incoming requests to their intended destinations, whether that be an upstream API server, a third-party service, a database, or even a serverless.

## How does an APISIX gateway secure your APIs?

API security at APISIX is the highest priority. As it serves as an inline proxy point of control over APIs.

- Verifies the identity associated with API requests through credential and token validation, as well as other authentication means. You can check available [authentication plugins](https://apisix.apache.org/plugins#authentication). 
- Determines which traffic is authorized to pass through the API to backend services.
- Controls the traffic flowing through the APIs using rate limiting and throttling. There are [traffic control plugins](https://apisix.apache.org/plugins#Traffic%20Control) available for that purpose.
- Observes all requests and applies runtime policies to enforce governance. There are many [observability plugins](https://apisix.apache.org/plugins#Observability) that can be easily enabled for your APIs.
- Implements all industry-standard encryptions.

![Apache APISIX API Gateway Security](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7dc8ii4f8ly4lchytr3i.png)
 
With the enough knowledge, now we can start to apply some security plugins in action for our demo Spring Boot App. Let's get started 🚀


## Clone the demo repository

For this demonstration, we’ll leverage the demo project [apisix-security-java-spring](https://github.com/Boburmirzo/apisix-security-java-spring) I prepared in advance. You can see it on Github. 

Use [git](https://git-scm.com/downloads) to clone the repository:

``` bash
git clone 'https://github.com/Boburmirzo/apisix-security-java-spring'
```

Go to root directory of _apisix-security-java-spring_

``` bash
cd apisix-security-java-spring
```

You can open the project in your favorite code editor. I used [IntelliJ Idea](https://www.jetbrains.com/idea/download/#section=windows) community edition. You’ll see the following project directory structure:

![APISIX project directory](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a73o7dp8fffkv4wqajun.png)
 

## Understand the structure of the project

In the project folders, here is the list of main components you can take a look at: 

- **APISIX's config files** - All the services are configured by mounting external configuration files in the project onto the docker containers: [/apisix_conf/conf.yaml](https://github.com/Boburmirzo/apisix-security-java-spring/blob/main/apisix_conf/config.yaml) defines the configs for apisix. Similarly, configs for etcd, prometheus, and grafana are located in [/etcd_conf/etcd.conf.yml](https://github.com/Boburmirzo/apisix-security-java-spring/blob/main/etcd_conf/etcd.conf.yml), [/prometheus_conf/prometheus.yml](https://github.com/Boburmirzo/apisix-security-java-spring/blob/main/prometheus_conf/prometheus.yml), and [/grafana_conf/config](https://github.com/Boburmirzo/apisix-security-java-spring/tree/main/grafana_conf/config) respectively.
- **APISIX's logs** - in the [apisix_log](https://github.com/Boburmirzo/apisix-security-java-spring/tree/main/apisix_log) folder, APISIX Admin and Control API requests are logged such as `access.log `or `error.log`
- **Spring Boot project** - Next, the [spring-postgres](https://github.com/Boburmirzo/apisix-security-java-spring/tree/main/spring-postgres) folder keeps Java application with **Spring framework** (_Controller, repository and entity classes_) and a **PostgreSQL database** setup with dummy schema and data. 

![spring-postgres project structure](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q7v09bncsgy2o2qcsm0z.png)
 
- **Docker compose file** - The [docker-compose.yml](https://github.com/Boburmirzo/apisix-security-java-spring/blob/main/docker-compose.yml) file defines an application with some services: 

 - `apisix-dashboard` - It runs [APISIX Dashboard](https://apisix.apache.org/docs/dashboard/USER_GUIDE) on port 9000. ℹ️ The Dashboard offers another way to interact with APISIX Admin API and we can achieve the same configuration result with the CLI as with the Dashboard. 

 - `apisix`- deploys APISIX and exposes it on port `9080`. On your local machine, you can access APISIX Admin API by sending requests to the following URL `http://127.0.0.1:9080/`
 - `etcd` - APISIX uses `etcd` to save and synchronize configuration.
 - `prometheus` - APISIX can fetch metrics data about upstream APIs together with [prometheus plugin](https://apisix.apache.org/docs/apisix/plugins/prometheus).
 - `grafana` - Metrics exported by the [prometheus plugin]https://apisix.apache.org/docs/apisix/plugins/prometheus can be also graphed in Grafana and you can see the running dashboard on port `3000`.
 - `backend` - While deploying the application, docker compose maps port `8080` of the backend service container to port `8080` of the host
 - `db` - PostgreSql database exposed on `5432` port that backend service connects, gets data from.

☝️You may notice all services are mapped to `apisix` network.

> The `apisix-security-java-spring` project makes use of the similar example [APISIX docker compose template](https://github.com/apache/apisix-docker/tree/master/example).

``` yaml
version: "3"

services:
  apisix-dashboard:
    image: apache/apisix-dashboard:2.10.1-alpine
    restart: always
    volumes:
    - ./dashboard_conf/conf.yaml:/usr/local/apisix-dashboard/conf/conf.yaml
    ports:
    - "9000:9000"
    networks:
      apisix:

  apisix:
    image: apache/apisix:2.13.1-alpine
    restart: always
    volumes:
      - ./apisix_log:/usr/local/apisix/logs
      - ./apisix_conf/config.yaml:/usr/local/apisix/conf/config.yaml:ro
    depends_on:
      - etcd
    ##network_mode: host
    ports:
      - "9080:9080/tcp"
      - "9091:9091/tcp"
      - "9443:9443/tcp"
      - "9092:9092/tcp"
    networks:
      apisix:

  etcd:
    image: bitnami/etcd:3.4.15
    restart: always
    volumes:
      - etcd_data:/bitnami/etcd
    environment:
      ETCD_ENABLE_V2: "true"
      ALLOW_NONE_AUTHENTICATION: "yes"
      ETCD_ADVERTISE_CLIENT_URLS: "http://0.0.0.0:2379"
      ETCD_LISTEN_CLIENT_URLS: "http://0.0.0.0:2379"
    ports:
      - "12379:2379/tcp"
    networks:
      apisix:

  prometheus:
    image: prom/prometheus:v2.25.0
    restart: always
    volumes:
      - ./prometheus_conf/prometheus.yml:/etc/prometheus/prometheus.yml
    ports:
      - "9090:9090"
    networks:
      apisix:

  grafana:
    image: grafana/grafana:7.3.7
    restart: always
    ports:
      - "3000:3000"
    volumes:
      - "./grafana_conf/provisioning:/etc/grafana/provisioning"
      - "./grafana_conf/dashboards:/var/lib/grafana/dashboards"
      - "./grafana_conf/config/grafana.ini:/etc/grafana/grafana.ini"
    networks:
      apisix:

  backend:
    build: spring-postgres/backend
    ports:
      - "8080:8080"
    environment:
      - POSTGRES_DB=example
    networks:
      apisix:

  db:
    image: postgres
    restart: always
    secrets:
      - db-password
    volumes:
      - db-data:/var/lib/postgresql/data
    networks:
      apisix:
    environment:
      - POSTGRES_DB=example
      - POSTGRES_PASSWORD_FILE=/run/secrets/db-password
    expose:
      - 5432
networks:
  apisix:
    driver: bridge
secrets:
  db-password:
    file: spring-postgres/db/password.txt
volumes:
  db-data:
  etcd_data:
    driver: local
```

## Build a multi-container APISIX via Docker CLI

Now we can start our application by running `docker compose` command from the root folder of the project:

``` bash
docker-compose -p docker-apisix up -d
```

Sample output:

``` bash

```

![docker compose run result](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tnkrllvkdbqzetb915ad.png)
 
The running container list you can see by running `docker compose ps` CLI command or using docker desktop:

![APISIX with other services on docker desktop](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bzai4z8ey61elaz0gydc.png)
 
Once the containers are running, navigate to `http://localhost:8080/get` in your web browser and you will see the following output:

![Hello Apache APISIX](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cp2sm3fryf0r1j3x3ssy.png)
 
This response actually is coming from the simple Spring Boot REST API. If you check the project folder `spring-postgres`, the `src/main/java` directory contains our project’s source code. There is a `GreetingController` controller class annotated with `@RestController` where single `/get` endpoint serves our requests. Basically, it retrieves the data from PostgreSQL database and replies to the request with `name` attribute in `Greeting` entity class.

We will enable different Apache APISIX security plugins for this endpoint in the next steps.

``` java
@RestController
public class GreetingController {

    private final GreetingRepository repository;

    public GreetingController(GreetingRepository repository) {
        this.repository = repository;
    }

    @GetMapping("/get")
    public String getApisixGreeting() {
        Greeting apisixGreeting = repository.findById(1).orElse(new Greeting("Not Found 😕"));
        return apisixGreeting.getName();
    }
}
```
![3 APISIX security plugins](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dys4l96pumbwvq4mal18.png)
 

## Enable IP addresses restriction

Now we can enable [IP restriction plugin](https://apisix.apache.org/docs/apisix/plugins/ip-restriction) for the API endpoint. IP restriction is a technique is to limit the IPs of clients that can send requests by whitelisting/blacklisting IP addresses list. We can not access the API with IPs other than the allowed ones.

The first step, we need to create a upstream for our backend service. We use `curl` commands to interact with APISIX's Admin API:

``` bash
curl "http://127.0.0.1:9080/apisix/admin/upstreams/1" -H "X-API-KEY: edd1c9f034335f136f87ad84b625c8f1" -X PUT -d '
{
  "type": "roundrobin",
  "nodes": {
    "backend:8080": 1
  }
}'
```

[Upstream object](https://apisix.apache.org/docs/apisix/terminology/upstream) will point to target `backend` service or multiple backend services, in our case, it is referencing to Spring Boot REST API.

In the next step, we define a [Route](https://apisix.apache.org/docs/apisix/terminology/route) and bind the route to the Upstream. It can be mapped by `upstream_id` in a specific Route. Also, we will enable `ip-restriction` plugin in the route configuration. 

``` bash
curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
    "uri": "/get",
    "plugins": {
	   "ip-restriction": {
            "whitelist": [
                "127.0.0.2"
            ]
        }
	},
    "upstream_id": "1"
}'
```

In the `whitelist` attribute of `ip-restriction` plugin, we are allowing only single IP address `127.0.0.2` to access `/get` URI. If you try to access the API with disallowed IP address, APISIX will throw a 403 Forbidden Http error:

``` bash
curl http://127.0.0.1:9080/get -i --interface 127.0.0.1
```

Sample output:

``` text
HTTP/1.1 403 Forbidden
Content-Type: text/plain; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Server: APISIX/2.13.1

{"message":"Your IP address is not allowed"}
```

☝️ Single IPs, multiple IPs or ranges in CIDR notation like 10.10.10.0/24 can be used in the plugin configuration. The plugin also supports IPv4 and IPv6 addresses.

Now we know one way to secure our APIs by allowing/disallowing IP addresses with APISIX. 

## Block direct access to an API resource

Next, let's see more enhanced URI blocking option with [uri-blocker plugin](https://apisix.apache.org/docs/apisix/plugins/uri-blocker) by applying regex filter rules. For example, we might have a scenario when an API endpoint should be blocked if an user is trying to access and download the private `sample-file.csv` file. 

We can enable `uri-blocker` plugin similar to `ip-restriction` plugin configuration we applied in the previous step:

``` bash
curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
    "uri": "/*",
    "plugins": {
	   "uri-blocker": {
		  "block_rules": ["sample-file.csv+"]
        }
	},
    "upstream_id": "1"
}'
```

Once you have configured the Plugin as shown above, you can try accessing the file with `curl` command:

```
curl -i http://127.0.0.1:9080/sample-file.csv
```

You should get another Http 403 Forbidden error:

``` text
HTTP/1.1 403 Forbidden
```

> Now we restricted direct access to a URI resource. You can also set a `rejected_msg` plugin config property to [customize the response body message](https://apisix.apache.org/docs/apisix/plugins/uri-blocker#:~:text=the%20block_rules.-,rejected_msg,-string).

## Allow users request the API

One of the techniques is to make corresponding access restrictions to API resources for specific users or services. [Consumer Restriction](https://apisix.apache.org/docs/apisix/plugins/consumer-restriction) (_consumer-restriction_) – makes corresponding access restrictions to your services or routes based on different users selected. If unknown user tries to send a request, Apache APISIX will meet them with an error.

Assume that we have two consumers of our APIs (`consumer1` and `consumer2`). We want to give an access only to one of them. To do so, let’s create two consumers and enable `consumer-restriction` plugin for our existing example route.

To create the first consumer, run the following command:

``` bash
curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -i -d '
{
    "username": "consumer1",
    "plugins": {
        "basic-auth": {
            "username":"consumer1",
            "password": "123456"
        }
    }
}'
```

To create the second consumer, run the following command:

``` bash
curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -i -d '
{
    "username": "consumer2",
    "plugins": {
        "basic-auth": {
            "username":"consumer2",
            "password": "654321"
        }
    }
}'
```

Then, enable `consumer-restriction` plugin and allow only `consumer1` access the `/get` endpoint:

```
curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
    "uri": "/get",
    "plugins": {
        "basic-auth": {},
        "consumer-restriction": {
            "whitelist": [
                "consumer1"
            ]
        }
    },
    "upstream_id": "1"
}'
```

The `consumer-restriction` has the type property which is enumerated type (it support the following values: _consumer_name, service_id_ and _route_id_), we can specify it to allow access to correspond object.

By default, the plugin returns a generic `{"message":"The consumer_name is forbidden."}` if the object is not allowed. It’s possible to configure a more friendly message via the plugin attribute.

Now we can test if the consumer `consumer1` can request the API resource with the provided credentials:

``` bash
curl -u consumer1:123456 http://127.0.0.1:9080/get -i
```

Output:

``` text
HTTP/1.1 200
Content-Type: text/plain;charset=UTF-8
Content-Length: 19
Connection: keep-alive
Server: APISIX/2.13.1

Hello Apache APISIX
```

It is obvious that `consumer1` can request the endpoint because we **whitelisted** the consumer in the `consumer-restriction` plugin config.

However, our the second consumer has no access. Now we can try to request the same path with different consumer credentials:

``` bash
curl -u consumer2:123456 http://127.0.0.1:9080/get -i
```

Output:

``` text
HTTP/1.1 403 Forbidden
Content-Type: text/plain; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Server: APISIX/2.13.1

{"message":"The consumer_name is forbidden."}
```

## What's next

In this blog post, we have demonstrated how to deploy our Spring Boot REST API app together with Apache APISIX API Gateway by harnessing Docker compose capability for smoother deployment. Also, we learned how to use basic security plugins so you can check other [available plugins](https://apisix.apache.org/plugins) to satisfy more advanced API security scenarios or create your own plugin for the API Gateway with the help of [Java Plugin Runner](https://apisix.apache.org/docs/java-plugin-runner/development/).

## Recommended content

➔ Watch Video Tutorial [Getting Started with Apache APISIX](https://youtu.be/dUOjJkb61so)  

➔ Read the blog post [Overview of Apache APISIX API Gateway Plugins](https://dev.to/apisix/overview-of-apache-apisix-api-gateway-plugins-2m8o)

➔ Read the blog post [Centralized Authentication with Apache APISIX Plugins](https://dev.to/apisix/centralized-authentication-with-apache-apisix-plugins-30fo)
 
➔ Read the blog post [API Security with OIDC by using Apache APISIX and Microsoft Azure AD](https://dev.to/apisix/api-security-with-oidc-by-using-apache-apisix-and-microsoft-azure-ad-50h3) 

➔ Read the blog post [API Observability with Apache APISIX Plugins](https://dev.to/apisix/apis-observability-with-apache-apisix-plugins-1bnm)

Community⤵️

🙋 [Join the Apache APISIX Community](https://apisix.apache.org/docs/general/community) 
🐦 [Follow us on Twitter](https://twitter.com/ApacheAPISIX)
📝 [Find us on Slack](https://join.slack.com/t/the-asf/shared_invite/zt-vlfbf7ch-HkbNHiU_uDlcH_RvaHv9gQ)
📧 [Mail to us](dev@apisix.apache.org) with your questions