{"id":18048078,"url":"https://github.com/ivangfr/spring-boot-nginx-keycloak-cluster","last_synced_at":"2026-04-04T12:57:31.390Z","repository":{"id":237592166,"uuid":"788643906","full_name":"ivangfr/spring-boot-nginx-keycloak-cluster","owner":"ivangfr","description":"The goal of this project is to use Nginx as a reverse proxy and load balancer for a Keycloak cluster with two instances and a Spring Boot application, called simple-service, also with two instances. The simple-service app will use Keycloak for IAM.","archived":false,"fork":false,"pushed_at":"2025-12-17T21:31:05.000Z","size":1130,"stargazers_count":22,"open_issues_count":0,"forks_count":6,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-12-21T09:05:08.648Z","etag":null,"topics":["docker","java","keycloak","nginx","oauth2-resource-server","postgresql","spring-boot","spring-security","spring-web-mvc"],"latest_commit_sha":null,"homepage":"","language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ivangfr.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"ivangfr"}},"created_at":"2024-04-18T20:08:18.000Z","updated_at":"2025-12-17T21:31:09.000Z","dependencies_parsed_at":"2024-05-02T18:59:00.038Z","dependency_job_id":"0c37639c-3a94-413c-864e-18f8cb751a61","html_url":"https://github.com/ivangfr/spring-boot-nginx-keycloak-cluster","commit_stats":null,"previous_names":["ivangfr/spring-boot-nginx-keycloak-cluster"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ivangfr/spring-boot-nginx-keycloak-cluster","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ivangfr%2Fspring-boot-nginx-keycloak-cluster","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ivangfr%2Fspring-boot-nginx-keycloak-cluster/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ivangfr%2Fspring-boot-nginx-keycloak-cluster/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ivangfr%2Fspring-boot-nginx-keycloak-cluster/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ivangfr","download_url":"https://codeload.github.com/ivangfr/spring-boot-nginx-keycloak-cluster/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ivangfr%2Fspring-boot-nginx-keycloak-cluster/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31400460,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-04T10:20:44.708Z","status":"ssl_error","status_checked_at":"2026-04-04T10:20:06.846Z","response_time":60,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["docker","java","keycloak","nginx","oauth2-resource-server","postgresql","spring-boot","spring-security","spring-web-mvc"],"created_at":"2024-10-30T20:11:20.038Z","updated_at":"2026-04-04T12:57:31.385Z","avatar_url":"https://github.com/ivangfr.png","language":"Shell","funding_links":["https://github.com/sponsors/ivangfr"],"categories":[],"sub_categories":[],"readme":"# spring-boot-nginx-keycloak-cluster\n\nThe goal of this project is to use [`Nginx`](https://nginx.org/en/) as a reverse proxy and load balancer for a [`Keycloak`](https://www.keycloak.org/) cluster with two instances and a [`Spring Boot`](https://docs.spring.io/spring-boot/index.html) application, called `simple-service`, also with two instances. The `simple-service` app will use `Keycloak` for IAM.\n\n## Proof-of-Concepts \u0026 Articles\n\nOn [ivangfr.github.io](https://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.\n\n## Additional Readings\n\n- \\[**Medium**\\] [**Using Nginx to Load Balance Requests to a Spring Boot Web application**](https://medium.com/@ivangfr/using-nginx-to-load-balance-requests-to-a-spring-boot-web-application-83a497a2f8ab)\n- \\[**Medium**\\] [**Using Nginx to Load Balance Requests to a Keycloak Cluster**](https://medium.com/@ivangfr/using-nginx-to-load-balance-requests-to-a-keycloak-cluster-52174c89a0e4)\n- \\[**Medium**\\] [**Nginx Load Balancing Requests to a Keycloak Cluster and a Spring Boot app that uses Keycloak as IAM**](https://medium.com/@ivangfr/nginx-load-balancing-requests-to-a-keycloak-cluster-and-a-spring-boot-app-that-uses-keycloak-as-iam-8e9e8280587d)\n\n## Project Diagram\n\n![project-diagram](documentation/project-diagram.jpeg)\n\n## Application\n\n- ### simple-service\n\n  `Spring Boot` Web Java application that exposes the following endpoints:\n  - `GET /api/public`: This endpoint is not secured; everyone can access it.\n  - `GET /api/secured`: This endpoint is secured and can only be accessed by users who provide a `JWT` access token issued by `Keycloak`. The token must include the role `APP_USER`.\n\n## Prerequisites\n\n- [`Java 25`](https://www.oracle.com/java/technologies/downloads/#java25) or higher.\n- A containerization tool (e.g., [`Docker`](https://www.docker.com), [`Podman`](https://podman.io), etc.)\n- [`jq`](https://jqlang.github.io/jq/)\n\n## Building simple-service Docker Image\n\n- In a terminal, navigate to the `spring-boot-nginx-keycloak-cluster` root folder.\n\n- Run the following script:\n  ```bash\n  ./build-docker-images.sh\n  ```\n\n## Configure /etc/hosts\n\nAdd the following line to `/etc/hosts`:\n```text\n127.0.0.1 keycloak-cluster.lb simple-service.lb\n```\n\n## Starting Environment\n\nOpen a terminal and inside the `spring-boot-nginx-keycloak-cluster` root folder run:\n```bash\n./init-environment.sh\n```\n\nThis script will start:\n- one `PostgreSQL` Docker container.\n- two `Keycloak` Docker containers.\n- two `simple-service` Docker containers.\n- one `Nginx` Docker container.\n\n## Configuring Keycloak\n\nWe can configure a client for `simple-service` in `Keycloak` using the `Keycloak` website at http://keycloak-cluster.lb. However, to keep things simple and fast, we have created a script for it.\n\nSo, in a terminal, make sure you are inside the `spring-boot-nginx-keycloak-cluster` root folder, run the script below:\n```bash\n./init-keycloak.sh\n```\n\nThe script will:\n- create `company-services` realm.\n- disable the required action `Verify Profile`.\n- create `simple-service` client.\n- create the client role `APP_USER` for the `simple-service` client.\n- create `USERS` group.\n- assign `APP_USER` client role to `USERS` group.\n- create `user-test` user.\n- assign `USERS` group to `user-test`.\n\nTo complete, copy the `SIMPLE_SERVICE_CLIENT_SECRET` value shown at the end of the script. It will be needed whenever we call `Keycloak` to get a JWT access token to access `simple-service`.\n\n## Testing the simple-service endpoints\n\n1. Open a new terminal.\n\n2. Call the endpoint `GET /public`:\n   ```bash\n   curl -i http://simple-service.lb/public\n   ```\n\n   It should return:\n   ```text\n   HTTP/1.1 200\n   ...\n   Hi World, I am a public endpoint\n   ```\n\n3. Try to call the endpoint `GET /secured` without authentication:\n   ```bash\n   curl -i http://simple-service.lb/secured\n   ```\n\n   It should return:\n   ```text\n   HTTP/1.1 401\n   ...\n   ```\n\n4. Create an environment variable that contains the `Client Secret` generated by `Keycloak` to `simple-service` at [Configure Keycloak](#configuring-keycloak) step:\n   ```bash\n   SIMPLE_SERVICE_CLIENT_SECRET=...\n   ```\n\n5. Run the command below to get an access token for `user-test` user:\n   ```bash\n   USER_TEST_ACCESS_TOKEN=\"$(curl -s -X POST \\\n     \"http://keycloak-cluster.lb/realms/company-services/protocol/openid-connect/token\" \\\n     -H \"Content-Type: application/x-www-form-urlencoded\" \\\n     -d \"username=user-test\" \\\n     -d \"password=123\" \\\n     -d \"grant_type=password\" \\\n     -d \"client_secret=$SIMPLE_SERVICE_CLIENT_SECRET\" \\\n     -d \"client_id=simple-service\" | jq -r .access_token)\"\n   echo $USER_TEST_ACCESS_TOKEN\n   ```\n\n6. Call the endpoint `GET /secured`:\n   ```bash\n   curl -i http://simple-service.lb/secured -H \"Authorization: Bearer $USER_TEST_ACCESS_TOKEN\"\n   ```\n\n   It should return:\n   ```text\n   HTTP/1.1 200\n   ...\n   Hi user-test, I am a secured endpoint\n   ```\n\n7. The default expiration period for the access token is `5 minutes`. So, wait for this time and then, using the same access token, try to call the secured endpoint.\n\n   It should return:\n   ```text\n   HTTP/1.1 401\n   ...\n   WWW-Authenticate: Bearer error=\"invalid_token\", error_description=\"An error occurred while attempting to decode the Jwt: Jwt expired at ...\", error_uri=\"https://tools.ietf.org/html/rfc6750#section-3.1\"\n   ...\n   ```\n\n8. Checking `Keycloak` and `simple-service` Docker container logs\n\n   We can verify that `Nginx` is appropriately load balancing the requests when an access token request to `Keycloak` is made. To view the `Keycloak` Docker container logs, execute the following commands in different terminals:\n   ```bash\n   docker logs -f keycloak1 keycloak2\n   docker logs -f keycloak2\n   ```\n\n   We can also verify that `Nginx` is appropriately load balancing requests to the `simple-service` endpoints. To view the `simple-service` Docker container logs, execute the following commands in different terminals:\n   ```bash\n   docker logs -f simple-service1\n   docker logs -f simple-service2\n   ```\n\n## Useful Links \u0026 Commands\n\n- **Keycloak**\n\n  The `Keycloak` website can be accessed at http://keycloak-cluster.lb\n\n- **Nginx**\n\n  If you wish to modify the `Nginx` configuration file without restarting its Docker container, follow these steps:\n  \n  - Apply the changes in the `nginx/nginx.conf` file.\n  - Execute the following command to access the `nginx` Docker container:\n    ```bash\n    docker exec -it nginx bash\n    ```\n  - In the `nginx` Docker container terminal, run:\n    ```bash\n    nginx -s reload\n    ```\n  - To exit, just run the command `exit`.\n\n## Shutdown\n\nTo stop and remove Docker containers, network, and volumes, open a terminal, navigate to the `spring-boot-nginx-keycloak-cluster` root folder, and run the following script:\n```bash\n./shutdown-environment.sh\n```\n\n## Cleanup\n\n- To remove the `simple-service` Docker image created, open a terminal, navigate to the `spring-boot-nginx-keycloak-cluster` root folder, and run the following script:\n  ```bash\n  ./remove-docker-images.sh\n  ```\n\n- Remove the line below from `/etc/hosts`:\n  ```text\n  127.0.0.1 keycloak-cluster.lb simple-service.lb\n  ```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fivangfr%2Fspring-boot-nginx-keycloak-cluster","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fivangfr%2Fspring-boot-nginx-keycloak-cluster","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fivangfr%2Fspring-boot-nginx-keycloak-cluster/lists"}