The goal of this project is to create a simple Spring Boot REST API and secure it with Kong using the LDAP Authentication and Basic Authentication plugins. Besides, we will explore more plugins that Kong offers, such as the Rate Limiting and Prometheus plugins.
On ivangfr.github.io, I have compiled my Proof-of-Concepts (PoCs) and articles. You can easily search for the technology you are interested in by using the filter. Who knows, perhaps I have already implemented a PoC or written an article about what you are looking for.
- [Medium] Using Kong to secure a Simple Spring Boot REST API with Basic Authentication plugin
- [Medium] Using Kong to secure a Simple Spring Boot REST API with LDAP Authentication plugin
- [Medium] Using Kong to configure Rate Limiting to a Simple Spring Boot REST API
- [Medium] Using Kong to secure a Simple Spring Boot REST API with Kong OIDC plugin and Keycloak
-
Spring BootJava Web application that exposes two endpoints:/api/public: that can be accessed by anyone; it is not secured./api/private: that must be accessed only by authenticated users.
-
Open a terminal and navigate to the
springboot-kong-pluginsroot folder. -
Run the command below to start:
./mvnw clean spring-boot:run --projects simple-service
-
Open another terminal and call the application endpoints:
curl -i localhost:8080/api/public curl -i localhost:8080/api/private curl -i localhost:8080/actuator/beans curl -i localhost:8080/actuator/health
-
To stop, go to the terminal where the application is running and press
Ctrl+C.
-
In a terminal, make sure you are in the
springboot-kong-pluginsroot folder. -
Build Docker Image
- JVM
./build-docker-images.sh
- Native
./build-docker-images.sh native
- JVM
-
In a terminal, run the following command:
docker run --rm -p 8080:8080 --name simple-service ivanfranchin/simple-service:1.0.0
-
Open another terminal and call the application endpoints:
curl -i localhost:8080/api/public curl -i localhost:8080/api/private curl -i localhost:8080/actuator/beans curl -i localhost:8080/actuator/health
-
To stop, go to the terminal where the application is running and press
Ctrl+C.
-
In a terminal, make sure you are in the
springboot-kong-pluginsroot folder. -
Run the following script:
./init-environment.sh
Note:
simple-serviceapplication is running as a Docker container. The container does not expose any port to the HOST machine. So, it cannot be accessed directly, forcing the caller to useKongas gateway server in order to access it.
The LDIF file that we will use, ldap/ldap-mycompany-com.ldif, has already a pre-defined structure for mycompany.com. Basically, it has 2 groups (developers and admin) and 4 users (Bill Gates, Steve Jobs, Mark Cuban and Ivan Franchin). Besides, it is defined that Bill Gates, Steve Jobs and Mark Cuban belong to the developers group, and Ivan Franchin belongs to the admin group.
Bill Gates > username: bgates, password: 123
Steve Jobs > username: sjobs, password: 123
Mark Cuban > username: mcuban, password: 123
Ivan Franchin > username: ifranchin, password: 123
There are two ways to import those users: by running a script or using phpldapadmin.
-
In another terminal, make sure you are in the
springboot-kong-pluginsroot folder. -
Run the following script:
./import-openldap-users.sh
-
Check users imported using
ldapsearch:ldapsearch -x -D "cn=admin,dc=mycompany,dc=com" \ -w admin -H ldap://localhost:389 \ -b "ou=users,dc=mycompany,dc=com" \ -s sub "(uid=*)"
-
Access https://localhost:6443
-
Login with the credentials:
Login DN: cn=admin,dc=mycompany,dc=com Password: admin -
Import the file
ldap/ldap-mycompany-com.ldif. -
You should see something like:
In a terminal, follow the steps below to configure Kong.
-
Before starting, check if
Kongadmin API is accessible:curl -I http://localhost:8001
It should return:
HTTP/1.1 200 OK
-
The following call will add the
simple-serviceservice:curl -i -X POST http://localhost:8001/services \ -d "name=simple-service" \ -d "protocol=http" \ -d "host=simple-service" \ -d "port=8080"
-
[Optional] To list all services run:
curl -i http://localhost:8001/services
-
One default route for the service, no specific
pathincluded:curl -i -X POST http://localhost:8001/services/simple-service/routes \ -d "name=simple-service-default" \ -d "protocols[]=http" \ -d "hosts[]=simple-service"
-
Another route specifically for
/api/privateendpoint (it will be secured and only accessible by LDAP users):curl -i -X POST http://localhost:8001/services/simple-service/routes \ -d "name=simple-service-api-private" \ -d "protocols[]=http" \ -d "hosts[]=simple-service" \ -d "paths[]=/api/private" \ -d "strip_path=false"
-
Finally, one route for
/actuator/beansendpoint (it will be secured and only accessible by pre-defined users):curl -i -X POST http://localhost:8001/services/simple-service/routes \ -d "name=simple-service-actuator-beans" \ -d "protocols[]=http" \ -d "hosts[]=simple-service" \ -d "paths[]=/actuator/beans" \ -d "strip_path=false"
-
[Optional] To list all
simple-serviceroutes run:curl -i http://localhost:8001/services/simple-service/routes
-
Call the
/api/publicendpoint:curl -i http://localhost:8000/api/public -H 'Host: simple-service'It should return:
HTTP/1.1 200 It is public. -
Call the
/api/privateendpoint:curl -i http://localhost:8000/api/private -H 'Host: simple-service'It should return:
HTTP/1.1 200 null, it is private.Note: This endpoint is not secured by the application, that is why the response is returned. The idea is to use
Kongto secure it. It will be done on the next steps. -
Call the
/actuator/beansendpoint:curl -i http://localhost:8000/actuator/beans -H 'Host: simple-service'It should return:
HTTP/1.1 200 {"contexts":{"simple-service":{"beans":...Note: As happened previously with
/api/private,/actuator/beansendpoint is not secured by the application. We will useKongto secure it on the next steps.
In this project, we are going to add these plugins: LDAP Authentication, Basic Authentication, Rate Limiting and Prometheus. Please refer to https://konghq.com/plugins for more.
The LDAP Authentication plugin will be used to secure the /api/private endpoint.
-
Add plugin to route
simple-service-api-private:curl -i -X POST http://localhost:8001/routes/simple-service-api-private/plugins \ -d "name=ldap-auth" \ -d "config.hide_credentials=true" \ -d "config.ldap_host=openldap" \ -d "config.ldap_port=389" \ -d "config.start_tls=false" \ -d "config.base_dn=ou=users,dc=mycompany,dc=com" \ -d "config.verify_ldap_host=false" \ -d "config.attribute=cn" \ -d "config.cache_ttl=60" \ -d "config.header_type=ldap"
-
Try to call the
/api/privateendpoint without credentials:curl -i http://localhost:8000/api/private -H 'Host: simple-service'It should return:
HTTP/1.1 401 Unauthorized { "message":"Unauthorized", "request_id":"..." } -
Call the
/api/privateendpoint using Bill Gates base64-encoded credentials:curl -i http://localhost:8000/api/private \ -H "Authorization:ldap $(echo -n 'Bill Gates':123 | base64)" \ -H 'Host: simple-service'
It should return:
HTTP/1.1 200 Bill Gates, it is private.
The Basic Authentication plugin will be used to secure the /actuator/beans endpoint.
-
Add plugin to route
simple-service-actuator-beans:curl -i -X POST http://localhost:8001/routes/simple-service-actuator-beans/plugins \ -d "name=basic-auth" \ -d "config.hide_credentials=true"
-
Try to call the
/actuator/beansendpoint without credentials:curl -i http://localhost:8000/actuator/beans -H 'Host: simple-service'It should return:
HTTP/1.1 401 Unauthorized { "message":"Unauthorized", "request_id":"..." } -
Create a consumer:
curl -i -X POST http://localhost:8001/consumers -d "username=ivanfranchin" -
Create a credential for consumer:
curl -i -X POST http://localhost:8001/consumers/ivanfranchin/basic-auth \ -d "username=ivan.franchin" \ -d "password=123"
-
Call the
/api/privateendpoint usingivan.franchincredentials:curl -i -u ivan.franchin:123 http://localhost:8000/actuator/beans -H 'Host: simple-service'It should return:
HTTP/1.1 200 {"contentDescriptor":{"providerVersion":... -
Let's create another consumer just for testing purposes:
curl -i -X POST http://localhost:8001/consumers -d "username=administrator" curl -i -X POST http://localhost:8001/consumers/administrator/basic-auth \ -d "username=administrator" \ -d "password=123"
We are going to add the following rate limits:
/api/publicand/actuator/health: one request per second./api/private: five requests a minute./actuator/beans: two requests a minute or 100 requests an hour.
-
Add plugin to route
simple-service-default:curl -i -X POST http://localhost:8001/routes/simple-service-default/plugins \ -d "name=rate-limiting" \ -d "config.second=1"
-
Add plugin to route
simple-service-api-private:curl -i -X POST http://localhost:8001/routes/simple-service-api-private/plugins \ -d "name=rate-limiting" \ -d "config.minute=5"
-
Add plugin to route
simple-service-actuator-beans:curl -i -X POST http://localhost:8001/routes/simple-service-actuator-beans/plugins \ -d "name=rate-limiting" \ -d "config.minute=2" \ -d "config.hour=100"
-
Make some calls to these endpoints.
-
Test
/api/public:curl -i http://localhost:8000/api/public -H 'Host: simple-service' curl -i http://localhost:8000/actuator/health -H 'Host: simple-service'
-
Test
/actuator/beans:curl -i -u ivan.franchin:123 http://localhost:8000/actuator/beans -H 'Host: simple-service' curl -i -u administrator:123 http://localhost:8000/actuator/beans -H 'Host: simple-service'
-
Test
/api/private:curl -i http://localhost:8000/api/private \ -H "Authorization:ldap $(echo -n 'Bill Gates':123 | base64)" \ -H 'Host: simple-service' curl -i http://localhost:8000/api/private \ -H "Authorization:ldap $(echo -n 'Mark Cuban':123 | base64)" \ -H 'Host: simple-service'
-
-
After exceeding some calls in a minute, you should see:
HTTP/1.1 429 Too Many Requests { "message":"API rate limit exceeded", "request_id":"..." }
-
Add plugin to
simple-service:curl -i -X POST http://localhost:8001/services/simple-service/plugins -d "name=prometheus" -
Make some requests to
simple-serviceendpoints. -
You can see some metrics:
curl -i http://localhost:8001/metrics
In a terminal and, inside the springboot-kong-plugins root folder, run the following script:
./shutdown-environment.shTo remove the Docker image created by this project, go to a terminal and, inside the springboot-kong-plugins root folder, run the script below:
./remove-docker-images.sh
