https://github.com/andrealcobia/spring-nginx-keycloak-postgres-stack
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.
https://github.com/andrealcobia/spring-nginx-keycloak-postgres-stack
docker docker-compose gradle keycloak postgresql spring-boot
Last synced: 5 months ago
JSON representation
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.
- Host: GitHub
- URL: https://github.com/andrealcobia/spring-nginx-keycloak-postgres-stack
- Owner: andrealcobia
- Created: 2024-08-12T15:47:09.000Z (almost 2 years ago)
- Default Branch: main
- Last Pushed: 2024-08-13T10:17:14.000Z (almost 2 years ago)
- Last Synced: 2025-04-07T01:14:10.786Z (about 1 year ago)
- Topics: docker, docker-compose, gradle, keycloak, postgresql, spring-boot
- Language: Java
- Homepage:
- Size: 321 KB
- Stars: 1
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# spring-nginx-keycloak-postgres
The 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.
## Project Diagram

## Application
- ### service-app
`Spring Boot` Web Java application that exposes the following endpoints:
- `GET /api/public`: This endpoint is not secured; everybody can access it;
- `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`.
## Prerequisites
- [`Java 17+`](https://www.oracle.com/java/technologies/downloads/#java17)
- [`Docker`](https://www.docker.com/)
- [`jq`](https://stedolan.github.io/jq)
## Building service-app Docker Image
- In a terminal, make sure you are inside the `service-app` root folder.
- Run the following :
```
./gradlew build
```
## Starting Environment
Open a terminal and inside the `spring-nginx-keycloak-postgres` root folder run:
```
docker-compose build && docker-compose up -d
```
This script will start:
- one `PostgreSQL` Docker container;
- one `Keycloak` Docker container;
- one `Service-app` Docker container;
- one `Nginx` Docker container;
## Configuring Keycloak
We 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.
So, in a terminal, make sure you are inside the `spring-nginx-keycloak-postgres` root folder, run the script below:
```
./init-keycloak.sh
```
The script will:
- create `company-services` realm;
- disable the required action `Verify Profile`;
- create `service-app` client;
- create the client role `APP_USER` for the `service-app` client;
- create `USERS` group;
- assign `APP_USER` client role to `USERS` group;
- create `user-test` user;
- assign `USERS` group to `user-test`;
To 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`.
## Testing the service-app endpoints
1. Open a new terminal;
2. Call the endpoint `GET /public`:
```
curl -i http://localhost:8082/public
```
It should return:
```
HTTP/1.1 200
...
Hi World, I am a public endpoint
```
3. Try to call the endpoint `GET /secured` without authentication:
```
curl -i http://localhost:8082/secured
```
It should return:
```
HTTP/1.1 401
...
```
4. Create an environment variable that contains the `Client Secret` generated by `Keycloak` to `service-app` at [Configure Keycloak](#configuring-keycloak) step:
```
SERVICE_APP_CLIENT_SECRET=...
```
5. Run the command below to get an access token for `user-test` user:
```
USER_TEST_ACCESS_TOKEN="$(curl -s -X POST \
"http://localhost:8081/realms/company-services/protocol/openid-connect/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "username=user-test" \
-d "password=123" \
-d "grant_type=password" \
-d "client_secret=$SERVICE_APP_CLIENT_SECRET" \
-d "client_id=service-app" | jq -r .access_token)"
echo $USER_TEST_ACCESS_TOKEN
```
6. Call the endpoint `GET /secured`:
```
curl -i http://localhost:8082/secured -H "Authorization: Bearer $USER_TEST_ACCESS_TOKEN"
```
It should return:
```
HTTP/1.1 200
...
Hi user-test, I am a secured endpoint
```
7. 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.
It should return:
```
HTTP/1.1 401
...
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"
...
```
## Useful Links & Commands
- **Keycloak**
The `Keycloak` website is at http://localhost:8080
- **Service-App**
The `Service-App` api is at http://localhost:9090
- **Nginx**
The `Nginx` website is at http://localhost:8081 for Keycloak endpoints and http://localhost:8082 for Service-App endpoints
## Cleanup
To remove the all docker image created, simply go to a terminal and run the following script:
```
./shutdown-environment.sh
```
## Wiki
- **Keycloak**
Keycloak 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.
```
healthcheck:
test:
[
"CMD-SHELL",
"exec 3<>/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\" >&3;grep \"HTTP/1.1 200 OK\" <&3",
]
```
The command you've provided is a Bash script that does the following:
1. **`exec 3<>/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.
2. **`echo -e "GET /health/ready HTTP/1.1\r\nhost: http://localhost\r\nConnection: close\r\n\r\n" >&3`**: 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.
3. **`grep "HTTP/1.1 200 OK" <&3`**: 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).
### Summary
This 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.
If the server returns anything other than `HTTP/1.1 200 OK`, the `grep` command would not return anything, indicating that the health check failed.