Securing a REST API with Spring Boot and Keycloak

Using role based access for secure endpoints and allowing anonymous access for public ones

Securing an application with Keycloak is relatively easy. Having both secured and public endpoints is not so straightforward. Here you will see how to achieve that in a few easy steps.

This post demonstrates the minimal set of configurations required to secure the REST endpoints of a Spring Boot application using Keycloak and how to allow anonymous access to the public endpoints.

Keycloak provides Identity and Access Management services. It’s open source and is compliant with the OpenID Connect, OAuth 2.0, and SAML protocols.

Some of its key features are:

  • Single-Sign On
  • Identity Brokering and Social Login
  • User Federation
  • Client Adapters
  • Admin Console
  • Account Management console

For more information check out Keycloak’s official page.

Setting up a Keycloak server

Docker will be used for the deployment of a Keycloak server.

If you’re not familiar with it you can refer to the standard way specified in the documentation which is pretty straightforward as well. You’ll just have to create an admin user separately.

The following command does a few things:

  • Starts the Keycloak server in the background;
  • Exposes the server on port 8081 on the host machine;
  • Creates an initial admin user with username `admin` and password `admin`
  • Specifies that Keycloak should use its embedded H2 database. H2 is the default DB_VENDOR value. The purpose of specifying it here is to illustrate that a database is used to store configurations, user information, etc. Supported databases are H2, MySQL, PostgreSQL, MariaDB and Oracle – for more details please check Keycloak documentation.
docker run -d  --name keycloak 
  --rm -p 8081:8080 
  -e KEYCLOAK_USER=admin 
  -e KEYCLOAK_PASSWORD=admin 
  -e DB_VENDOR=h2 
  jboss/keycloak

To verify that Keycloak container is running execute `docker ps`. The container status should be `UP`:

95dd64a63178        jboss/keycloak      "/opt/jboss/tools/do..."   5 seconds ago       Up 4 seconds        0.0.0.0:8081->8080/tcp   keycloak

You should now be able to open Keycloak admin UI on http://localhost:8081 and you can login with admin:admin in the master realm.

Configuring Keycloak

Keycloak can be configured entirely through its UI but in order to allow the process to be automated we are going to use the Keycloak Admin REST API . We will send a request to the REST API with a JSON file, containing the keycloak configuration, in the request body.

Bellow we describe the minimal set of objects that you have to create in Keycloak:

  • Realm – A realm manages a set of users, credentials, roles and groups. A user belongs to and logs into a realm. Realms are isolated from one another and can only manage and authenticate the users that they control.

In this example we will define the user roles on the realm level rather than the client level. Realm roles can be assigned to any user belonging to any client.

  • Client – Clients are entities that can request Keycloak to authenticate a user. Most often, clients are applications and services that want to use Keycloak to secure themselves and provide a single sign-on solution. Clients can also be entities that just want to request identity information or an access token so that they can securely invoke other services on the network that are secured by Keycloak.

The client will correspond to our Spring Boot application which we aim to secure.

  • Role – Roles identify a type or category of user. `Admin`, `user`, `manager` and `employee` are all typical roles that may exist in an organization. Applications often assign access and permissions to specific roles rather than individual users as dealing with users can be too fine grained and hard to manage.
  • User – Users are entities that are able to log into your system. They can have attributes associated with themselves like email, username, address, phone number, and birthday. They can be assigned group membership and have specific roles assigned to them.

This is the JSON configuration that we will be using:

{
 "realm": "spring-keycloak",
 "enabled": true,
 "clients": [
   {
     "clientId": "resource-service",
     "enabled": true,
     "secret": "secret",
     "directAccessGrantsEnabled": true
   }
 ],
 "roles": {
   "realm": [
     {
       "name": "authenticated-user",
       "description": "Authenticated user privileges"
     }
   ]
 },
 "users": [
   {
     "username": "Tom",
     "enabled": true,
     "credentials": [
       {
         "type": "password",
         "value": "password"
       }
     ],
     "realmRoles": [
       "authenticated-user"
     ]
   }
 ]
}

The configuration is pretty straightforward there are only two things to note here:

  • We assign `authenticated-user` role to our test user – Tom;
  • We set the `directAccessGrantsEnabled` to true which gives the client direct access to the user credentials and enables it to exchange them for access token. This is the equivalent of OAuth2 Password Grant

The following script configures Keycloak:

#!/usr/bin/env bash

# Get Access token
# In the request we provide:
# 1. Тhe `admin-cli` client which is a public client created by default;
# 2. The username and password of the user we created during the initialization of the Keycloak server
# 3. Password as grant type
json=$(curl 
 -d "client_id=admin-cli" 
 -d "username=admin" 
 -d "password=admin" 
 -d "grant_type=password" 
 "http://localhost:8081/auth/realms/master/protocol/openid-connect/token") 
&& token=$(echo $json | sed "s/{.*"access_token":"([^"]*).*}/1/g") 
&& echo "token = $token"

#Configure Keycloak
# In the request we provide:
# 1. `application/json` as Content type;
# 2. The Access token received as response from the previous request
# 3. The name of the file in which the JSON configuration is stored
curl  -X POST 
 -H "Content-Type: application/json" 
 -H "Authorization: bearer $token" 
 --data "@realm-configuration.json" 
 "http://localhost:8081/auth/admin/realms"

You can verify that the configuration succeeded from the Keycloak UI.

Spring Boot application

Our spring Boot application has two endpoints /public and /protected. All endpoints will be secured by default but we will allow anonymous access to the /public one. This is how the Controller looks like:

@RestController
public class ResourceController {

 @GetMapping(value = "/public")
 public ResponseEntity<String> getPublicResource() {
   return ResponseEntity.ok("This is a public resource");
 }

 @GetMapping(value = "/protected")
 public ResponseEntity<String> getProtectedResource() {
   return ResponseEntity.ok("This is a protected resource");
 }

}

In order to integrate Keycloak with our application we need the Keycloak Spring Boot adapter JAR.
https://gitlab.com/datastork-examples/spring-keycloak-example/blob/master/build.gradle

dependencyManagement {
 imports {
   mavenBom "org.keycloak.bom:keycloak-adapter-bom:6.0.1"
 }
}

dependencies {
 implementation "org.keycloak:keycloak-spring-boot-starter"
}

And then we need to configure the application:
https://gitlab.com/datastork-examples/spring-keycloak-example/blob/master/src/main/resources/application.yml

keycloak:
  realm: spring-keycloak #the name of the realm
  auth-server-url: http://localhost:8081/auth #Keycloak server endpoint
  ssl-required: external #localhost and private IPs can access without HTTPS
  resource: resource-service #the client
  credentials:
    secret: secret #the client secret
  bearer-only: true #do not redirect to login page
  securityConstraints[0]:
    securityCollections[0]:
      name: public endpoints
      patterns[0]: /public/* #public endpoint; no role is associated with it
  securityConstraints[1]:
    authRoles[0]: authenticated-user #user must have `authenticated-user` role to access the resource
    securityCollections[0]:
      name: protected endpoints
      patterns[0]: /* #secure all endpoints by default

Note that the `/public` endpoint does not have a role associated with it and therefore anonymous access to it  is allowed. The `authenticates-user` role is associated with all other endpoints which will require the one accessing them to have the `authenticates-user` role.

Testing public and private endpoints:

  1. Public endpoint
curl -X GET http://localhost:8080/public

2. Private endpoint:
First get an Access token:
In the request we should specify:

  • `resource-service` as client id
  • `secret` as client secret
  • `Tom` as username
  • `password` as password
  • `password` as grant type

All of the arguments specified above (except grant type) are the Keycloak entities we created during the configuration step.

curl 
 -d "client_id=resource-service" 
 -d "client_secret=secret" 
 -d "username=Tom" 
 -d "password=password" 
 -d "grant_type=password" 
 "http://localhost:8081/auth/realms/spring-keycloak/protocol/openid-connect/token"

Use Access token received as response from previous request.

curl -X GET 
  http://localhost:8080/protected 
  -H "Authorization: Bearer eyJhbGciOiJSUzI..."

Should receive a response similar to this:

% Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                Dload  Upload   Total   Spent    Left  Speed
100    28  100    28    0     0     28      0  0:00:01 --:--:--  0:00:01 28000This is a protected resource

Conclusion

In this post we’ve seen how to deploy and configure a Keycloak server, how to secure all endpoints by default of a Spring Boot REST application with Keycloak and how to allow anonymous access to some endpoints. This way you can be sure that you can add any number of new endpoints and they will be secured. Publicly available will be only those explicitly exposed.

The code can be found here.

Share

Share on facebook
Share on google
Share on twitter
Share on linkedin
Share on pinterest
Share on print
Share on email