springboot-kong-keycloak
springboot-kong-keycloak copied to clipboard
Goal: create a Spring Boot app called book-service accessible only through the Kong API gateway. In Kong, the kong-oidc plugin will be installed, enabling communication between Kong and Keycloak. This...
springboot-kong-keycloak
The goal is to create a Spring Boot application to manage books, called book-service and secure it by using Kong API gateway and Keycloak OpenID Connect Provider.
Note: In
kubernetes-minikube-environmentrepository, it's shown how to deploy this project inKubernetes(Minikube)
Proof-of-Concepts & Articles
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.
Additional Readings
- [Medium] Using Kong to secure a Simple Spring Boot REST API with Kong OIDC plugin and Keycloak
- [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] Implementing and Securing a Simple Spring Boot REST API with Keycloak
Project Diagram

As we can see from the diagram, book-service will only be reachable through Kong API gateway.
In Kong, it's installed kong-oidc plugin that will enable the communication between Kong and Keycloak OpenID Connect Provider.
This way, when Kong receives a request to book-service, it will validate together with Keycloak whether it's a valid request.
Also, before redirecting to the request to the upstream service, a Serverless Function (post-function) will get the access token present in the X-Userinfo header provided by kong-oidc plugin, decode it, extract the username and preferred_username, and enrich the request with these two information before sending to book-service
Application
-
book-service
Spring BootREST API application to manages books. The API doesn't have any security.book-serviceusesMongoDBas storage.Endpoints
GET /actuator/health GET /api/books POST /api/books {"isbn": "...", "title": "..."} GET /api/books/{isbn} DELETE /api/books/{isbn}
Prerequisites
Run application during development using Maven
-
Open a terminal and navigate to
springboot-kong-keycloakroot folder -
Run the command below to start
mongodbDocker containerdocker run -d --name mongodb -p 27017:27017 mongo:7.0.6 -
Run the command below to start
book-service./mvnw clean spring-boot:run --projects book-service -
Open another terminal and call application endpoints
curl -i localhost:9080/api/books curl -i -X POST localhost:9080/api/books -H "Content-Type: application/json" -d '{"isbn":"123", "title":"Kong & Keycloak"}' curl -i localhost:9080/api/books/123 curl -i -X DELETE localhost:9080/api/books/123 curl -i localhost:9080/actuator/health -
To stop
book-service, go to the terminal where it's running and pressCtrl+CmongodbDocker container, go to a terminal and run the following commanddocker rm -fv mongodb
Build application Docker Image
-
In a terminal, make sure you are in
springboot-kong-keycloakroot folder -
Build Docker Image
- JVM
./docker-build.sh - Native
./docker-build.sh native
Environment Variable Description MONGODB_HOSTSpecify host of the Mongodatabase to use (defaultlocalhost)MONGODB_PORTSpecify port of the Mongodatabase to use (default27017) - JVM
Test application Docker Image
-
In a terminal, create a Docker network
docker network create springboot-kong-keycloak-net -
Run the command below to start
mongodbDocker containerdocker run -d --name mongodb -p 27017:27017 --network springboot-kong-keycloak-net mongo:7.0.6 -
Run the following command to start
book-serviceDocker containerdocker run --rm -p 9080:9080 --name book-service -e MONGODB_HOST=mongodb --network springboot-kong-keycloak-net ivanfranchin/book-service:1.0.0 -
Open another terminal and call application endpoints
curl -i localhost:9080/api/books curl -i -X POST localhost:9080/api/books -H "Content-Type: application/json" -d '{"isbn":"123", "title":"Kong & Keycloak"}' curl -i localhost:9080/api/books/123 curl -i -X DELETE localhost:9080/api/books/123 curl -i localhost:9080/actuator/health -
To stop
book-service, go to the terminal where it's running and pressCtrl+CmongodbDocker container, go to a terminal and run the following commanddocker rm -fv mongodb- remove Docker network
docker network rm springboot-kong-keycloak-net
Initialize Environment
-
In a terminal, make use you are in
springboot-kong-keycloakroot folder -
Run the following script
./init-environment.sh
Note:
book-serviceapplication is running as a Docker container. The container does not expose any port to HOST machine. So, it cannot be accessed directly, forcing the caller to useKongas gateway server in order to access it.
Configure Keycloak
-
In a terminal, make sure you are in
springboot-kong-keycloakroot folder -
Run the following script to configure
Keycloakforbook-serviceapplication./init-keycloak.shThis script creates:
company-servicesrealm;book-serviceclient;- user with username
ivan.franchinand password123.
-
The
book-serviceclient secret (BOOK_SERVICE_CLIENT_SECRET) is shown at the end of the execution. It will be used in the next step -
You can check the configuration in
Keycloakby accessing http://localhost:8080. The credentials areadmin/admin.
Configure Kong
-
In a terminal, make sure you are in
springboot-kong-keycloakroot folder -
Create an environment variable that contains the
Client Secretgenerated byKeycloaktobook-serviceat Configure Keycloak stepBOOK_SERVICE_CLIENT_SECRET=... -
Run the following script to configure
Kongforbook-serviceapplication./init-kong.sh $BOOK_SERVICE_CLIENT_SECRETThis script creates:
- service to
book-service; - route to
/actuatorpath; - route to
/apipath; - add
kong-oidcplugin to route of/apipath. It will authenticate users againstKeycloakOpenID Connect Provider; - add
serverless function (post-function)plugin to route of/apipath. It gets the access token present in theX-Userinfoheader provided bykong-oidcplugin, decoded it, extracts theusernameandpreferred_username, and enriches the request with these two information before sending tobook-service.
- service to
Testing
-
Try to call the public
GET /actuator/healthendpointcurl -i localhost:8000/actuator/health -H 'Host: book-service'It should return
HTTP/1.1 200 {"status":"UP"} -
Try to call the private
GET /api/booksendpoint without access tokencurl -i localhost:8000/api/books -H 'Host: book-service'It should return
HTTP/1.1 401 Unauthorized no Authorization header found -
Get
ivan.franchinaccess tokenACCESS_TOKEN=$(./get-access-token.sh $BOOK_SERVICE_CLIENT_SECRET) && echo $ACCESS_TOKENNote: In
jwt.io, you can decode and verify theJWTaccess token -
Call again the private
GET /api/booksendpoint using the access tokencurl -i localhost:8000/api/books \ -H 'Host: book-service' \ -H "Authorization: Bearer $ACCESS_TOKEN"It should return
HTTP/1.1 200 [] -
You can try other endpoints using access token
Create book
curl -i -X POST localhost:8000/api/books \ -H 'Host: book-service' \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Content-Type: application/json" -d '{"isbn": "123", "title": "Kong & Keycloak"}'Get book
curl -i localhost:8000/api/books/123 \ -H 'Host: book-service' \ -H "Authorization: Bearer $ACCESS_TOKEN"Delete book
curl -i -X DELETE localhost:8000/api/books/123 \ -H 'Host: book-service' \ -H "Authorization: Bearer $ACCESS_TOKEN"
Useful Links & Commands
-
MongoDB
List books
docker exec -it mongodb mongo bookdb db.books.find()Type
exitto get out of MongoDB shell
Shutdown
In a terminal and, inside springboot-kong-keycloak root folder, run the following script
./shutdown-environment.sh
Cleanup
To remove the Docker image created by this project, in a terminal and, inside springboot-kong-keycloak root folder, run the script below
./remove-docker-images.sh
References
- https://www.jerney.io/secure-apis-kong-keycloak-1/
- https://github.com/d4rkstar/kong-konga-keycloak
Issues
-
Unable to upgrade to
kongversion3.xbecauseBasePluginclass was deprecated inkongversion2.4.xand removed in version3.0.xlink. Now,kong-oidcneeds to supportkongversion3.xissue; -
When upgrading
postgresto a version above13.x(using current kong version), there is an error while runningkong[error] 1#0: init_by_lua error: /usr/local/share/lua/5.1/pgmoon/init.lua:273: module 'openssl.rand' not found:No LuaRocks module found for openssl.rand no field package.preload['openssl.rand'] no file './openssl/rand.lua' no file './openssl/rand/init.lua' no file '/usr/local/openresty/site/lualib/openssl/rand.ljbc' no file '/usr/local/openresty/site/lualib/openssl/rand/init.ljbc' no file '/usr/local/openresty/lualib/openssl/rand.ljbc' no file '/usr/local/openresty/lualib/openssl/rand/init.ljbc' no file '/usr/local/openresty/site/lualib/openssl/rand.lua' no file '/usr/local/openresty/site/lualib/openssl/rand/init.lua' no file '/usr/local/openresty/lualib/openssl/rand.lua' no file '/usr/local/openresty/lualib/openssl/rand/init.lua' no file '/usr/local/openresty/luajit/share/luajit-2.1.0-beta3/openssl/rand.lua' no file '/usr/local/share/lua/5.1/openssl/rand.lua' no file '/usr/local/share/lua/5.1/openssl/rand/init.lua' no file '/usr/local/openresty/luajit/share/lua/5.1/openssl/rand.lua' no file '/usr/local/openresty/luajit/share/lua/5.1/openssl/rand/init.lua' no file '/home/kong/.luarocks/share/lua/5.1/openssl/rand.lua' no file '/home/kong/.luarocks/share/lua/5.1/openssl/rand/init.lua' no file '/usr/local/openresty/site/lualib/openssl/rand.so' no file '/usr/local/openresty/lualib/openssl/rand.so' no file './openssl/rand.so' no file '/usr/local/lib/lua/5.1/openssl/rand.so' no file '/usr/local/openresty/luajit/lib/lua/5.1/openssl/rand.so' no file '/usr/local/lib/lua/5.1/loadall.so' no file '/home/kong/.luarocks/lib/lua/5.1/openssl/rand.so' no file '/usr/local/openresty/site/lualib/openssl.so' no file '/usr/local/openresty/lualib/openssl.so' no file './openssl.so' no file '/usr/local/lib/lua/5.1/openssl.so' no file '/usr/local/openresty/luajit/lib/lua/5.1/openssl.so' no file '/usr/local/lib/lua/5.1/loadall.so' no file '/home/kong/.luarocks/lib/lua/5.1/openssl.so' stack traceback: [C]: in function 'require' /usr/local/share/lua/5.1/pgmoon/init.lua:273: in function 'auth' /usr/local/share/lua/5.1/pgmoon/init.lua:213: in function 'connect' .../share/lua/5.1/kong/db/strategies/postgres/connector.lua:216: in function 'connect' .../share/lua/5.1/kong/db/strategies/postgres/connector.lua:516: in function 'query' .../share/lua/5.1/kong/db/strategies/postgres/connector.lua:284: in function 'init' /usr/local/share/lua/5.1/kong/db/init.lua:141: in function 'init_connector' /usr/local/share/lua/5.1/kong/init.lua:503: in function 'init' init_by_lua:3: in main chunk nginx: [error] init_by_lua error: /usr/local/share/lua/5.1/pgmoon/init.lua:273: module 'openssl.rand' not found:No LuaRocks module found for openssl.rand no field package.preload['openssl.rand'] no file './openssl/rand.lua' no file './openssl/rand/init.lua' no file '/usr/local/openresty/site/lualib/openssl/rand.ljbc' no file '/usr/local/openresty/site/lualib/openssl/rand/init.ljbc' no file '/usr/local/openresty/lualib/openssl/rand.ljbc' no file '/usr/local/openresty/lualib/openssl/rand/init.ljbc' no file '/usr/local/openresty/site/lualib/openssl/rand.lua' no file '/usr/local/openresty/site/lualib/openssl/rand/init.lua' no file '/usr/local/openresty/lualib/openssl/rand.lua' no file '/usr/local/openresty/lualib/openssl/rand/init.lua' no file '/usr/local/openresty/luajit/share/luajit-2.1.0-beta3/openssl/rand.lua' no file '/usr/local/share/lua/5.1/openssl/rand.lua' no file '/usr/local/share/lua/5.1/openssl/rand/init.lua' no file '/usr/local/openresty/luajit/share/lua/5.1/openssl/rand.lua' no file '/usr/local/openresty/luajit/share/lua/5.1/openssl/rand/init.lua' no file '/home/kong/.luarocks/share/lua/5.1/openssl/rand.lua' no file '/home/kong/.luarocks/share/lua/5.1/openssl/rand/init.lua' no file '/usr/local/openresty/site/lualib/openssl/rand.so' no file '/usr/local/openresty/lualib/openssl/rand.so' no file './openssl/rand.so' no file '/usr/local/lib/lua/5.1/openssl/rand.so' no file '/usr/local/openresty/luajit/lib/lua/5.1/openssl/rand.so' no file '/usr/local/lib/lua/5.1/loadall.so' no file '/home/kong/.luarocks/lib/lua/5.1/openssl/rand.so' no file '/usr/local/openresty/site/lualib/openssl.so' no file '/usr/local/openresty/lualib/openssl.so' no file './openssl.so' no file '/usr/local/lib/lua/5.1/openssl.so' no file '/usr/local/openresty/luajit/lib/lua/5.1/openssl.so' no file '/usr/local/lib/lua/5.1/loadall.so' no file '/home/kong/.luarocks/lib/lua/5.1/openssl.so' stack traceback: [C]: in function 'require' /usr/local/share/lua/5.1/pgmoon/init.lua:273: in function 'auth' /usr/local/share/lua/5.1/pgmoon/init.lua:213: in function 'connect' .../share/lua/5.1/kong/db/strategies/postgres/connector.lua:216: in function 'connect' .../share/lua/5.1/kong/db/strategies/postgres/connector.lua:516: in function 'query' .../share/lua/5.1/kong/db/strategies/postgres/connector.lua:284: in function 'init' /usr/local/share/lua/5.1/kong/db/init.lua:141: in function 'init_connector' /usr/local/share/lua/5.1/kong/init.lua:503: in function 'init' init_by_lua:3: in main chunk