Introduction
In this walkthrough, we are going to look at some of the Apache APISIX API Gateway built-in plugins for securing your Spring Boot REST APIs 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. It contains also conceptual knowledge about 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 in action.
- ✅ Block direct access to some API resources with Uri-blocker plugin.
- ✅ Make sure that only allowed users can request your API with Consumer restriction plugin.
Key things we need:
- 👉 JDK 11+. Make sure that you have OpenJDK package installed in your system. For this demonstration, I used Windows OS and Java 11.
- 👉 Docker desktop - you need also Docker desktop installed locally to complete this tutorial. It is available for Windows or macOS. Or install the Docker ACI Integration CLI for 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 (MFA) or tokens like JWT Web Tokens.
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 is an essential component of an 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 is a lightweight open-source API gateway 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.
- 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 available for that purpose.
- Observes all requests and applies runtime policies to enforce governance. There are many observability plugins that can be easily enabled for your APIs.
- Implements all industry-standard encryptions.
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 projectapisix-security-java-spring I prepared in advance. You can see it on Github.
Use git to clone the repository:
git clone 'https://github.com/Boburmirzo/apisix-security-java-spring'
Go to root directory of apisix-security-java-spring
cd apisix-security-java-spring
You can open the project in your favorite code editor. I used IntelliJ Idea community edition. You’ll see the following project directory structure:
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 defines the configs for apisix. Similarly, configs for etcd, prometheus, and grafana are located in /etcd_conf/etcd.conf.yml, /prometheus_conf/prometheus.yml, and /grafana_conf/config respectively.
- APISIX's logs - in the apisix_log folder, APISIX Admin and Control API requests are logged such as
access.log
orerror.log
- Spring Boot project - Next, the spring-postgres folder keeps Java application with Spring framework (Controller, repository and entity classes) and a PostgreSQL database setup with dummy schema and data.
Docker compose file - The docker-compose.yml file defines an application with some services:
apisix-dashboard
- It runs APISIX Dashboard 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 port9080
. On your local machine, you can access APISIX Admin API by sending requests to the following URLhttp://127.0.0.1:9080/
etcd
- APISIX usesetcd
to save and synchronize configuration.prometheus
- APISIX can fetch metrics data about upstream APIs together with prometheus plugin.grafana
- Metrics exported by the [prometheus plugin]apisix.apache.org/docs/apisix/plugins/prome.. can be also graphed in Grafana and you can see the running dashboard on port3000
.backend
- While deploying the application, docker compose maps port8080
of the backend service container to port8080
of the hostdb
- PostgreSql database exposed on5432
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.
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:
docker-compose -p docker-apisix up -d
Sample output:
The running container list you can see by running docker compose ps
CLI command or using docker desktop:
Once the containers are running, navigate to http://localhost:8080/get
in your web browser and you will see the following output:
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.
@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();
}
}
Enable IP addresses restriction
Now we can enable IP restriction plugin 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:
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 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 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.
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:
curl http://127.0.0.1:9080/get -i --interface 127.0.0.1
Sample output:
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 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:
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:
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.
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 (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:
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:
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, serviceid and _routeid), 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:
curl -u consumer1:123456 http://127.0.0.1:9080/get -i
Output:
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:
curl -u consumer2:123456 http://127.0.0.1:9080/get -i
Output:
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 to satisfy more advanced API security scenarios or create your own plugin for the API Gateway with the help of Java Plugin Runner.
Recommended content
➔ Watch Video Tutorial Getting Started with Apache APISIX
➔ Read the blog post Overview of Apache APISIX API Gateway Plugins
➔ Read the blog post Centralized Authentication with Apache APISIX Plugins
➔ Read the blog post API Security with OIDC by using Apache APISIX and Microsoft Azure AD
➔ Read the blog post API Observability with Apache APISIX Plugins
Community⤵️
- 🙋 Join the Apache APISIX Community
- 🐦 Follow us on Twitter
- 📝 Find us on Slack
- 📧 Mail to us with your questions