The goal of this project is to create a simple Spring-Boot REST API, called simple-service
, and secure it with
Keycloak
. Furthermore, the API users will be loaded into Keycloak
from
OpenLDAP
server.
Spring-Boot Java Web application that exposes two endpoints:
/api/public
: endpoint that can be access by anyone, it is not secured;/api/private
: endpoint that can just be accessed by users that provides aJWT
token issued byKeycloak
and the token must contain the roleUSER
.
-
Open one terminal
-
Inside
springboot-keycloak-openldap
root folder run
docker-compose up -d
-
Wait a little bit until
MySQL
andKeycloak
containers areUp (healthy)
-
In order to check the status of the containers run the command
docker-compose ps
The LDIF
file that we will use, springboot-keycloak-openldap/ldap/ldap-mycompany-com.ldif
, contains already a
pre-defined structure for mycompany.com
. Basically, it has 2 groups (developers
and admin
) and 4 users (Bill Gates
, Steve Jobs
, Mark Cuban
and Ivan Franchin
). Besides, it is defined that Bill Gates
, Steve Jobs
and
Mark Cuban
belong to developers
group and Ivan Franchin
belongs to admin
group.
Bill Gates > username: bgates, password: 123
Steve Jobs > username: sjobs, password: 123
Mark Cuban > username: mcuban, password: 123
Ivan Franchin > username: ifranchin, password: 123
There are two ways to import those users: by running a script; or by using phpldapadmin
In springboot-keycloak-openldap
root folder run
./import-openldap-users.sh
-
Access https://localhost:6443
-
Login with the credentials
Login DN: cn=admin,dc=mycompany,dc=com
Password: admin
- Import the file
springboot-keycloak-openldap/ldap/ldap-mycompany-com.ldif
Run the command below to check the users imported. It uses ldapsearch
ldapsearch -x -D "cn=admin,dc=mycompany,dc=com" \
-w admin -H ldap://localhost:389 \
-b "ou=users,dc=mycompany,dc=com" \
-s sub "(uid=*)"
- Access http://localhost:8080/auth/admin/
- Login with the credentials
Username: admin
Password: admin
- 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
.
- Click on
Clients
menu on the left. - Click
Create
button. - On
Client ID
field typesimple-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
typeUSER
. - Click on
Save
.
- Click on the
User Federation
menu on the left. - Select
ldap
. - On
Vendor
field selectOther
- On
Connection URL
typeldap://<ldap-host>
.
ldap-host
can be obtained running the following command on a terminaldocker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' ldap-host
- Click on
Test connection
button, to check if the connection is OK. - On
Users DN
typeou=users,dc=mycompany,dc=com
- On
Bind DN
typecn=admin,dc=mycompany,dc=com
- On
Bind Credential
setadmin
- Click on
Test authentication
button, to check if the authentication is OK. - On
Custom User LDAP Filter
set(gidnumber=500)
to just get developers. - Click on
Save
. - Click on
Synchronize all users
.
- Click on
Users
menu on the left. - Click on
View all users
. 3 users will be shown. - Edit user
bgates
. - Go to
Role Mappings
tab. - Select
simple-service
on the combo-boxClient Roles
. - Add the role
USER
tobgates
. - Do the same for the user
sjobs
. - Let's leave
mcuban
withoutUSER
role.
-
Open a new terminal
-
In
springboot-keycloak-openldap
root folder, run the command below to startsimple-service
application
./mvnw clean spring-boot:run --projects simple-service -Dspring-boot.run.jvmArguments="-Dserver.port=9080"
Note: In order to run some commands/scripts, you must have jq
installed on you machine
-
Open a new terminal
-
Call the endpoint
GET /api/public
curl -i http://localhost:9080/api/public
It will return
HTTP/1.1 200
It is public.
- Try to call the endpoint
GET /api/private
without authentication
curl -i http://localhost:9080/api/private
It will return
HTTP/1.1 302
Here, the application is trying to redirect the request to an authentication link.
- Export to
SIMPLE_SERVICE_CLIENT_SECRET
environment variable theClient Secret
generated by Keycloak forsimple-service
(Configuring Keycloak > Create a new Client).
export SIMPLE_SERVICE_CLIENT_SECRET=...
- Run the command below to get an access token for
bgates
user.
BGATES_ACCESS_TOKEN=$(curl -s -X POST \
"http://localhost:8080/auth/realms/company-services/protocol/openid-connect/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "username=bgates" \
-d "password=123" \
-d "grant_type=password" \
-d "client_secret=$SIMPLE_SERVICE_CLIENT_SECRET" \
-d "client_id=simple-service" | jq -r .access_token)
- Call the endpoint
GET /api/private
curl -i -H "Authorization: Bearer $BGATES_ACCESS_TOKEN" http://localhost:9080/api/private
It will return
HTTP/1.1 200
bgates, it is private.
- Run the command below to get an access token for
mcuban
user.
MCUBAN_ACCESS_TOKEN=$(curl -s -X POST \
"http://localhost:8080/auth/realms/company-services/protocol/openid-connect/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "username=mcuban" \
-d "password=123" \
-d "grant_type=password" \
-d "client_secret=$SIMPLE_SERVICE_CLIENT_SECRET" \
-d "client_id=simple-service" | jq -r .access_token )
- Try to call the endpoint
GET /api/private
curl -i -H "Authorization: Bearer $MCUBAN_ACCESS_TOKEN" http://localhost:9080/api/private
As mcuban
does not have the USER
role, he cannot access this endpoint. The endpoint return will be
HTTP/1.1 403
{
"timestamp":"2018-12-26T13:14:10.493+0000",
"status":403,
"error":"Forbidden",
"message":"Forbidden",
"path":"/api/private"
}
-
Go to
Keycloak
and add the roleUSER
to themcuban
. -
Run the command on
step 7)
again to get a new access token formcuban
user. -
Call again the endpoint
GET /api/private
using thecurl
command presented onstep 8
. It will return
HTTP/1.1 200
mcuban, it is private.
- The access token default expiration period is
5 minutes
. So, wait for this time and, using the same access token, try to call the private endpoint. It will return
HTTP/1.1 401
WWW-Authenticate: Bearer realm="company-services", error="invalid_token", error_description="Token is not active"
You can get an access token to simple-service
using client_id
and client_secret
- Go to
Keycloak
. - Select
company-services
realm (if it is not already selected). - Click on
Clients
on the left menu. - Select
simple-service
client. - On
Settings
tab, turnON
the fieldService Accounts Enabled
. - Click on
Save
. - On
Service Account Roles
tab. - Select
simple-service
on the combo-boxClient Roles
. - Add the role
USER
. - Go to a terminal and run the commands
CLIENT_ACCESS_TOKEN=$(curl -s -X POST \
"http://localhost:8080/auth/realms/company-services/protocol/openid-connect/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_secret=$SIMPLE_SERVICE_CLIENT_SECRET" \
-d "client_id=simple-service" | jq -r .access_token)
- Try to call the endpoint
GET /api/private
curl -i http://localhost:9080/api/private -H "authorization: Bearer $CLIENT_ACCESS_TOKEN"
It will return
HTTP/1.1 200
service-account-simple-service, it is private.
-
Click on
GET /api/public
to open it. Then, click onTry it out
button and, finally, click onExecute
button It will return
Code: 200
Response Body: It is public.
-
Now click on
GET /api/private
, it is a secured endpoint. Let's try it without authentication. -
Click on
Try it out
button and then onExecute
button It will return
TypeError: Failed to fetch
- In order to access the private endpoint, you need an access token. To get it, run the following commands in a terminal.
BGATES_ACCESS_TOKEN="Bearer $(curl -s -X POST \
"http://localhost:8080/auth/realms/company-services/protocol/openid-connect/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "username=bgates" \
-d "password=123" \
-d "grant_type=password" \
-d "client_secret=$SIMPLE_SERVICE_CLIENT_SECRET" \
-d "client_id=simple-service" | jq -r .access_token)"
echo $BGATES_ACCESS_TOKEN
-
Copy the token generated (something like that starts with
Bearer ...
) and go back toSwagger
. -
Click on the
Authorize
button, paste the access token (copied previously) in the value field. Then, click onAuthorize
and, to finalize, click onClose
. -
Go to
GET /api/private
, click onTry it out
and then onExecute
button It will return
Code: 200
Response Body: bgates, it is private.
- Build Docker Image
./mvnw clean package dockerfile:build -DskipTests --projects simple-service
Environment Variable | Description |
---|---|
KEYCLOAK_HOST |
Specify host of the Keycloak to use (default localhost ) |
KEYCLOAK_PORT |
Specify port of the Keycloak to use (default 8080 ) |
- Run
simple-service
docker container, joining it to docker-compose network
docker run -d --rm -p 9080:8080 \
--name simple-service \
--network=springboot-keycloak-openldap_default \
--env KEYCLOAK_HOST=keycloak \
docker.mycompany.com/simple-service:1.0.0
- Export to
SIMPLE_SERVICE_CLIENT_SECRET
environment variable theClient Secret
generated by Keycloak tosimple-service
(Configuring Keycloak > Create a new Client).
export SIMPLE_SERVICE_CLIENT_SECRET=...
- Run the command below to get an access token for
bgates
user.
BGATES_ACCESS_TOKEN=$(
docker exec -t -e CLIENT_SECRET=$SIMPLE_SERVICE_CLIENT_SECRET keycloak bash -c '
curl -s -X POST \
http://keycloak:8080/auth/realms/company-services/protocol/openid-connect/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "username=bgates" \
-d "password=123" \
-d "grant_type=password" \
-d "client_secret=$CLIENT_SECRET" \
-d "client_id=simple-service" | jq -r .access_token ')
- Call the endpoint
GET /api/private
curl -i -H "Authorization: Bearer $BGATES_ACCESS_TOKEN" http://localhost:9080/api/private
- To stop
springboot-keycloak-openldap
docker container, run
docker stop simple-service
To stop and remove containers, networks and volumes
docker-compose down -v
With jwt.io you can inform the JWT token received from Keycloak and the online tool decodes the token, showing its header and payload. "# spring-boot-keycloack-ldap-docker"