{"id":15147475,"url":"https://github.com/andrealcobia/spring-nginx-keycloak-postgres-stack","last_synced_at":"2026-01-19T09:01:18.359Z","repository":{"id":252933430,"uuid":"841536225","full_name":"andrealcobia/spring-nginx-keycloak-postgres-stack","owner":"andrealcobia","description":"The goal of this project is to use Nginx as a reverse proxy and load balancer for a Keycloak and a Spring Boot application, called service-app. The service-app app will use Keycloak for IAM.","archived":false,"fork":false,"pushed_at":"2024-08-13T10:17:14.000Z","size":329,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-07T01:14:10.786Z","etag":null,"topics":["docker","docker-compose","gradle","keycloak","postgresql","spring-boot"],"latest_commit_sha":null,"homepage":"","language":"Java","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/andrealcobia.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"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}},"created_at":"2024-08-12T15:47:09.000Z","updated_at":"2024-08-13T13:54:56.000Z","dependencies_parsed_at":"2024-08-13T14:29:39.395Z","dependency_job_id":"d9a3094a-662b-44d8-8f2b-1fe78225e0ba","html_url":"https://github.com/andrealcobia/spring-nginx-keycloak-postgres-stack","commit_stats":null,"previous_names":["andrealcobia/spring-nginx-keycloak-postgres-stack"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/andrealcobia/spring-nginx-keycloak-postgres-stack","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andrealcobia%2Fspring-nginx-keycloak-postgres-stack","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andrealcobia%2Fspring-nginx-keycloak-postgres-stack/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andrealcobia%2Fspring-nginx-keycloak-postgres-stack/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andrealcobia%2Fspring-nginx-keycloak-postgres-stack/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/andrealcobia","download_url":"https://codeload.github.com/andrealcobia/spring-nginx-keycloak-postgres-stack/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andrealcobia%2Fspring-nginx-keycloak-postgres-stack/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28565001,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-19T08:53:44.001Z","status":"ssl_error","status_checked_at":"2026-01-19T08:52:40.245Z","response_time":67,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: 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","docker-compose","gradle","keycloak","postgresql","spring-boot"],"created_at":"2024-09-26T12:41:43.115Z","updated_at":"2026-01-19T09:01:18.342Z","avatar_url":"https://github.com/andrealcobia.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# spring-nginx-keycloak-postgres\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/) and a [`Spring Boot`](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/) application, called `service-app`. The `service-app` app will use `Keycloak` for IAM.\n\n## Project Diagram\n\n![project-diagram](documentation/project-diagram.png)\n\n## Application\n\n- ### service-app\n\n  `Spring Boot` Web Java application that exposes the following endpoints:\n\n  - `GET /api/public`: This endpoint is not secured; everybody 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 contain the role `APP_USER`.\n\n## Prerequisites\n\n- [`Java 17+`](https://www.oracle.com/java/technologies/downloads/#java17)\n- [`Docker`](https://www.docker.com/)\n- [`jq`](https://stedolan.github.io/jq)\n\n## Building service-app Docker Image\n\n- In a terminal, make sure you are inside the `service-app` root folder.\n\n- Run the following :\n  ```\n  ./gradlew build\n  ```\n\n## Starting Environment\n\nOpen a terminal and inside the `spring-nginx-keycloak-postgres` root folder run:\n\n```\ndocker-compose build \u0026\u0026 docker-compose up -d\n```\n\nThis script will start:\n\n- one `PostgreSQL` Docker container;\n- one `Keycloak` Docker container;\n- one `Service-app` Docker container;\n- one `Nginx` Docker container;\n\n## Configuring Keycloak\n\nWe can configure a client for `service-app` in `Keycloak` by using `Keycloak` website at http://localhost:8080. However, to keep things simple and fast, we've created a script for it.\n\nSo, in a terminal, make sure you are inside the `spring-nginx-keycloak-postgres` root folder, run the script below:\n\n```\n./init-keycloak.sh\n```\n\nThe script will:\n\n- create `company-services` realm;\n- disable the required action `Verify Profile`;\n- create `service-app` client;\n- create the client role `APP_USER` for the `service-app` 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 `SERVICE_APP_CLIENT_SECRET` value that is shown at the end of the script. It will be needed whenever we call `Keycloak` to get a `JWT` access token to access `service-app`.\n\n## Testing the service-app endpoints\n\n1. Open a new terminal;\n\n2. Call the endpoint `GET /public`:\n\n   ```\n   curl -i http://localhost:8082/public\n   ```\n\n   It should return:\n\n   ```\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\n   ```\n   curl -i http://localhost:8082/secured\n   ```\n\n   It should return:\n\n   ```\n   HTTP/1.1 401\n   ...\n   ```\n\n4. Create an environment variable that contains the `Client Secret` generated by `Keycloak` to `service-app` at [Configure Keycloak](#configuring-keycloak) step:\n\n   ```\n   SERVICE_APP_CLIENT_SECRET=...\n   ```\n\n5. Run the command below to get an access token for `user-test` user:\n\n   ```\n   USER_TEST_ACCESS_TOKEN=\"$(curl -s -X POST \\\n     \"http://localhost:8081/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=$SERVICE_APP_CLIENT_SECRET\" \\\n     -d \"client_id=service-app\" | jq -r .access_token)\"\n   echo $USER_TEST_ACCESS_TOKEN\n   ```\n\n6. Call the endpoint `GET /secured`:\n\n   ```\n   curl -i http://localhost:8082/secured -H \"Authorization: Bearer $USER_TEST_ACCESS_TOKEN\"\n   ```\n\n   It should return:\n\n   ```\n   HTTP/1.1 200\n   ...\n   Hi user-test, I am a secured endpoint\n   ```\n\n7. The access token default expiration period is `5 minutes`. So, wait for this time and, using the same access token, try to call the secured endpoint.\n\n   It should return:\n\n   ```\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\n## Useful Links \u0026 Commands\n\n- **Keycloak**\n\n  The `Keycloak` website is at http://localhost:8080\n\n- **Service-App**\n  The `Service-App` api is at http://localhost:9090\n\n- **Nginx**\n  The `Nginx` website is at http://localhost:8081 for Keycloak endpoints and http://localhost:8082 for Service-App endpoints\n\n## Cleanup\n\nTo remove the all docker image created, simply go to a terminal and run the following script:\n\n```\n./shutdown-environment.sh\n```\n\n## Wiki\n\n- **Keycloak**\n\nKeycloak containers don't come with curl or wget in it, this forces the users to use alternative mechanisms to realise health checks for the keycloak standard containers.\n\n```\n      healthcheck:\n      test:\n        [\n          \"CMD-SHELL\",\n          \"exec 3\u003c\u003e/dev/tcp/127.0.0.1/9000;echo -e \\\"GET /health/ready HTTP/1.1\\r\\nhost: http://localhost\\r\\nConnection: close\\r\\n\\r\\n\\\" \u003e\u00263;grep \\\"HTTP/1.1 200 OK\\\" \u003c\u00263\",\n        ]\n```\n\nThe command you've provided is a Bash script that does the following:\n\n1. **`exec 3\u003c\u003e/dev/tcp/127.0.0.1/9000`**: This opens a TCP connection to `127.0.0.1` (localhost) on port `9000` and assigns file descriptor 3 for both reading and writing to that connection.\n\n2. **`echo -e \"GET /health/ready HTTP/1.1\\r\\nhost: http://localhost\\r\\nConnection: close\\r\\n\\r\\n\" \u003e\u00263`**: This sends an HTTP `GET` request to the `/health/ready` endpoint of the server running on `127.0.0.1:9000`. The `\\r\\n` sequences represent carriage return and line feed, which are required to properly format the HTTP request.\n\n3. **`grep \"HTTP/1.1 200 OK\" \u003c\u00263`**: This reads the response from the server via the previously opened connection (file descriptor 3) and searches for the string `\"HTTP/1.1 200 OK\"`. This indicates that the server responded with a successful HTTP status code (200 OK).\n\n### Summary\n\nThis script essentially checks if a server running on `localhost:9000` is healthy by making an HTTP GET request to `/health/ready`. If the server responds with `HTTP/1.1 200 OK`, it implies that the health check passed.\n\nIf the server returns anything other than `HTTP/1.1 200 OK`, the `grep` command would not return anything, indicating that the health check failed.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandrealcobia%2Fspring-nginx-keycloak-postgres-stack","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fandrealcobia%2Fspring-nginx-keycloak-postgres-stack","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandrealcobia%2Fspring-nginx-keycloak-postgres-stack/lists"}