springboot-testing-mongodb-keycloak
The goals of this project are:
- Create a
Spring Boot
application that manages books, calledbook-service
; - Use
Keycloak
as authentication and authorization server; - Test using
Testcontainers
; - Explore the utilities and annotations that
Spring Boot
provides when testing applications.
Note: In
kubernetes-environment
repository, it is shown how to deploy this project inKubernetes
(Minikube
)
Prerequisites
Application
-
book-service
Spring Boot
Web application that manages books.MongoDB
is used as storage, and the application has its sensitive endpoints (add, update and delete books) secured.
Start Environment
-
Open a terminal and inside
springboot-testing-mongodb-keycloak
root folder rundocker-compose up -d
-
Wait a little bit until all containers are
Up (healthy)
. You can check their status by runningdocker-compose ps
Configure Keycloak
There are two ways: running a script or using Keycloak
website
Running script
-
In a terminal, make sure you are in
springboot-testing-mongodb-keycloak
root folder -
Run the following script to configure
Keycloak
forbook-service
application./init-keycloak.sh
This script creates
company-services
realm,book-service
client,manage_books
client role and the userivan.franchin
with the rolemanage_books
assigned. -
Copy the
BOOK_SERVICE_CLIENT_SECRET
value printed at the end. It will be needed whenever we callKeycloak
to get a token to accessbook-service
Using Keycloak website
Login
-
Login with the credentials
Username: admin Password: admin
Create a new Realm
- Go to top-left corner and hover the mouse over
Master
realm. A blue buttonAdd realm
will appear. Click on it - On
Name
field, writecompany-services
. Click onCreate
Create a new Client
- Click on
Clients
menu on the left - Click
Create
button - On
Client ID
field typebook-service
- Click on
Save
- On
Settings
tab, set theAccess Type
toconfidential
- Still on
Settings
tab, set theValid Redirect URIs
tohttp://localhost:9080
- Click on
Save
- Go to
Credentials
tab. Copy the value onSecret
field. It will be used on the next steps - Go to
Roles
tab - Click
Add Role
button - On
Role Name
typemanage_books
- Click on
Save
Create a new User
- Click on
Users
menu on the left - Click on
Add User
button - On
Username
field setivan.franchin
- Click on
Save
- Go to
Credentials
tab - Set to
New Password
andPassword Confirmation
the value123
- Turn off the
Temporary
field - Click on
Reset password
- Confirm the pop up clicking on
Change Password
- Go to
Role Mappings
tab - Select
book-service
on the combo-boxClient Roles
- Add the role
manage_books
toivan.franchin
Running book-service with Gradle
-
Open a new terminal and navigate to
springboot-testing-mongodb-keycloak
root folder -
Run the following command to start the application
./gradlew book-service:clean book-service:bootRun \ --args='--server.port=9080 --spring.data.mongodb.username=bookuser --spring.data.mongodb.password=bookpass'
-
The application's swagger URL is http://localhost:9080/swagger-ui.html
Getting Access Token
-
Open a terminal and make sure you are in
springboot-testing-mongodb-keycloak
root folder -
Create an environment variable that contains the
Client Secret
generated byKeycloak
tobook-service
at Configure Keycloak stepBOOK_SERVICE_CLIENT_SECRET=...
-
Run the commands below to get an access token for
ivan.franchin
ACCESS_TOKEN=$(./get-access-token.sh $BOOK_SERVICE_CLIENT_SECRET) echo $ACCESS_TOKEN
-
The access token has a default expiration time of
5 minutes
Test using cURL
-
In terminal, call the endpoint
GET /api/books
curl -i http://localhost:9080/api/books
It will return:
HTTP/1.1 200 []
-
Try to call the endpoint
POST /api/books
, without access tokencurl -i -X POST http://localhost:9080/api/books \ -H "Content-Type: application/json" \ -d '{ "authorName": "ivan", "title": "java 8", "price": 10.5 }'
It will return:
HTTP/1.1 302
-
If you do not have the access token stored in
ACCESS_TOKEN
environment variable, get it by following the steps describe at Getting Access Token -
Call the endpoint
POST /api/books
, now informing the access tokencurl -i -X POST http://localhost:9080/api/books \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "authorName": "ivan", "title": "java 8", "price": 10.5 }'
It will return something like
HTTP/1.1 201 { "id":"01d984be-26bc-49f5-a201-602293d62b82", "authorName":"ivan", "title":"java 8", "price":10.5 }
Test using Swagger
-
Click on
GET /api/books
to open it. Then, click onTry it out
button and, finally, onExecute
button.It will return a http status code
200
and an empty list or a list with some books if you've already added them -
Now, let's try to call a secured endpoint without authentication. Click on
POST /api/books
to open it. Then, click onTry it out
button (you can use the default values) and, finally, onExecute
button.It will return:
TypeError: Failed to fetch
-
Get the access token as explained at Getting Access Token
-
Copy the token generated and go back to
Swagger
-
Click on the
Authorize
button and paste access token (copied previously) in the value field. Then, click onAuthorize
and, to finalize, click onClose
-
Go to
POST /api/books
, click onTry it out
and then onExecute
button.It will return something like
HTTP/1.1 201 { "id": "5cf212c3-7902-4141-968b-82ae7a3443f1", "authorName": "Craig Walls", "title": "Spring Boot", "price": 10.5 }
Running book-service as a Docker Container
-
Build Docker Image
./gradlew book-service:clean book-service:jibDockerBuild -x test -x integrationTest
Environment Variable Description MONGODB_HOST
Specify host of the Mongo
database to use (defaultlocalhost
)MONGODB_PORT
Specify port of the Mongo
database to use (default27017
)KEYCLOAK_HOST
Specify host of the Keycloak
to use (defaultlocalhost
)KEYCLOAK_PORT
Specify port of the Keycloak
to use (default8080
) -
Run
book-service
docker container, joining it to docker-compose networkdocker run -d --rm -p 9080:8080 \ --name book-service \ --network=springboot-testing-mongodb-keycloak_default \ --env MONGODB_HOST=mongodb \ --env KEYCLOAK_HOST=keycloak \ --env SPRING_DATA_MONGODB_USERNAME=bookuser \ --env SPRING_DATA_MONGODB_PASSWORD=bookpass \ docker.mycompany.com/book-service:1.0.0
-
Create an environment variable that contains the
Client Secret
generated byKeycloak
BOOK_SERVICE_CLIENT_SECRET=...
-
In order to get the access token from
Keycloak
, run the following commands.Note: the
keycloak
string is informed in the second argument of the script. It changeslocalhost
host inside the script. This way, we won't have the error complaining about an invalid token due to an invalid token issuer.ACCESS_TOKEN=$(./get-access-token.sh $BOOK_SERVICE_CLIENT_SECRET keycloak) echo $ACCESS_TOKEN
-
Test using cURL or Swagger are the same as explained above
Useful Links & Commands
-
MongoDB
List all books
docker exec -it mongodb mongo -ubookuser -pbookpass --authenticationDatabase bookdb use bookdb db.books.find()
Type
exit
to get out of MongoDB shell -
jwt.io
With jwt.io you can inform the JWT token received from
Keycloak
and the online tool decodes the token, showing its header and payload.
Shutdown
-
Stop
book-service
- If it was started with
Gradle
, go to the terminal where the application is running and pressCtrl+C
- If it was started as a Docker container, run the command below
docker stop book-service
- If it was started with
-
To stop and remove docker-compose containers, networks and volumes, make sure you are in
springboot-testing-mongodb-keycloak
and rundocker-compose down -v
Running Unit and Integration Tests
-
In a terminal and inside
springboot-testing-mongodb-keycloak
root folder, run the command below to run unit and integration tests./gradlew book-service:clean book-service:assemble book-service:cleanTest \ book-service:test book-service:integrationTest
Note: During integration tests,
Testcontainers
will start automaticallyMongoDB
andKeycloak
containers before the tests begin and shuts them down when the tests finish. -
From
springboot-testing-mongodb-keycloak
root folder, Unit Testing Report can be found atbook-service/build/reports/tests/test/index.html
-
From
springboot-testing-mongodb-keycloak
root folder, Integration Testing Report can be found atbook-service/build/reports/tests/integrationTest/index.html
References
Issues
- Disabled
BookControllerTest
because I am getting the exception below. TheadapterConfig
parameter passed tointernalBuild
isnull
java.lang.NullPointerException at org.keycloak.adapters.KeycloakDeploymentBuilder.internalBuild(KeycloakDeploymentBuilder.java:57) at org.keycloak.adapters.KeycloakDeploymentBuilder.build(KeycloakDeploymentBuilder.java:202) at org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver.resolve(KeycloakSpringBootConfigResolver.java:39) at org.keycloak.adapters.springsecurity.config.KeycloakSpringConfigResolverWrapper.resolve(KeycloakSpringConfigResolverWrapper.java:40) at org.keycloak.adapters.AdapterDeploymentContext.resolveDeployment(AdapterDeploymentContext.java:89) ...