Securing JAX-RS API with Microprofile JWT + Keycloak using OAUTH 2.0 Authorization Code Flow

In this blog post, we will secure our RESTful web service using Microprofile JWT and Keycloak.

Keycloak is an open source authentication and authorization server from Redhat JBoss. Keycloak can be downloaded from https://www.keycloak.org/downloads.html. The keycloak version used for this blog post is keycloak-7.0.0 standalone server distribution.

For a detailed explanation of JWT and Microprofile JWT, please refer to this blog post https://www.tomitribe.com/blog/microprofile-json-web-token-jwt/

Setting up the Keycloak Server

Unzip the downloaded keycloak standalone server distribution and navigate to the keycloak folder using a terminal or command prompt and type the following command

./bin/standalone.sh -Djboss.http.port=8084

The above command starts the keycloak server on port 8084. Next open a browser window and navigate to http://localhost:8084/auth to create an administrators account. the user interface for the keycloak server looks like this

Keycloak User Interface

After creating the administrator account, you can now log in by clicking the Administration Console > and logging in with the account details you created.

Keycloak Login page

Next we create a new realm to manage our application and users. To create a new realm, Hover on the select realm dropdown and click on the add realm button. For this application, I have named the realm, customerapp as shown in the screenshot below

Next, We create a client for our Ionic/Angular app by clicking on the Clients tab on the keycloak admin interface and clicking on the create button. In this example, I have named the client spa-customerapp

After saving, we can see all the client’s configuration options. Next we update the client configuration form as shown in the screenshot below

The customer-client client is configured to use opienid-connect as the client protocol using a public access type and redirecting URI’s to the root of our angular/ionic app at http://localhost:8100/*. The following fields have been updated on the Clients configuration form as follows

  • Client ID – customer-client
  • Client Protocol – openid-connect
  • Access Type – public
  • Standard Flow Enabled – ON (highlighted in blue)
  • Direct Access Grants Enabled – ON
  • Valid Redirect URI’s – http://localhost:8100/* (This is the address of our angular/ionic client)
  • Web Origins – *

After entering the details above, Click on the save button at the bottom of the form.

Next, we create ROLES which is used to provide access control to the RESTful resources used in our application. Click on Roles and create new Roles called USER and ADMIN. This would be used to provide RBAC (Role Based Access Control) to our REST endpoints.

Roles creation

Next, we create the USERS for our application. This USERS would have access to the REST endpoints in our application that will be secured depending on the roles assigned to the user.

Next, click on the credentials tab and assign a new password for the newly created user. Click on the Reset password button to save the credentials.

The following USERS have been added

  • The admin user with credentials password as the password
  • The staff user with credentials password as the password

Next, click on the Role Mappings tab and assign the users to their respective roles. In this example, The admin user is assigned to the ADMIN role while the staff user is assigned to the USER role.

Finally, click on Clients > customer-clients and select the Mappers tab and click on the Add Builtin button at the top-right corner of Mappers interface and add the group mappers to the customer-client as shown in the screenshot below

That’s all for the keycloak configuration. Next we have to secure our REST endpoint using Microprofile JWT RBAC.

SECURE THE JAX-RS BACK END

In Payara server, the microprofile JWT is provided by add the following dependencies to your pom.xml

<dependency>
            <groupId>org.eclipse.microprofile</groupId>
            <artifactId>microprofile</artifactId>
            <version>3.0</version>
            <type>pom</type>
            <scope>provided</scope>
</dependency>

Next step is to mark the JAX-RS Application class as requiring microprofile JWT RBAC. This is done by add the @LoginConfig annotation to the Application class with authMethod = “MP-JWT” as shown in the code below

@ApplicationPath("api")
@LoginConfig(authMethod = "MP-JWT", realmName="MP-JWT")
public class ApplicationConfig extends Application {

   
}

Next we will secure the REST endpoints by adding the @RolesAllowed to the methods that will be secured. The @RolesAllowed annotation needs to match the roles we created in the keycloak server which allows authorization on methods where the security constraint has been declared. The methods in CustomerEndPoint class has been updated with the following security roles as shown in the code below

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package omos.microsystems.customerapp;

import java.net.URI;
import javax.annotation.security.RolesAllowed;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;
import javax.ws.rs.core.UriInfo;
import org.eclipse.microprofile.jwt.JsonWebToken;

/**
 *
 * @author omozegieaziegbe
 */
@RequestScoped
@Path("customers")
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
@Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
//@DenyAll
public class CustomerEndpoint {

    @Inject
    CustomerService customerService;

    @RolesAllowed({"ADMIN","USER"})
    @GET
    public Response getAll() {   
        return Response.ok(customerService.getAll()).build();
    }

    @RolesAllowed({"ADMIN"})
    @GET
    @Produces({MediaType.APPLICATION_JSON})
    @Path("{id}")
    public Response getCustomer(@PathParam("id") Integer id, @Context UriInfo uriInfo) {
        Customers customer = customerService.findById(id);
        return Response.ok(customer).build();
    }

    @RolesAllowed({"ADMIN","USER"})
    @POST
    public Response create(Customers customer) throws Exception {
        customerService.create(customer);
        URI location = new URI("http://localhost:8080/api/customers/" + customer.getId());
        return Response.created(location).build();
//        return Response.ok().build();
    }

    @RolesAllowed({"ADMIN"})
    @PUT
    @Path("{id}")
    public Response update(@PathParam("id") Integer id, Customers customer) {
        Customers updateCustomer = customerService.findById(id);
        updateCustomer.setFullname(customer.getFullname());
        updateCustomer.setAddress(customer.getAddress());
        updateCustomer.setCourse(customer.getCourse());
        updateCustomer.setCity(customer.getCity());
        updateCustomer.setEmail(customer.getEmail());
        customerService.update(updateCustomer);
        return Response.ok().build();
    }

    @RolesAllowed({"ADMIN"})
    @DELETE
    @Path("{id}")
    public Response delete(@PathParam("id") Integer id) {
        Customers getCustomer = customerService.findById(id);
        customerService.delete(getCustomer);
        return Response.ok().build();
    }
    
}

From the code above,

  • The getAll() method is allowed to users belonging to both the “ADMIN” and “USER” Role.
  • The getCustomer() method is allowed only to users belonging to the “ADMIN” Role.
  • The create() method is allowed to users belonging to the “ADMIN” and “USER” Role.
  • The update() method is allowed only to users belonging to the “ADMIN” Role.
  • The delete() method is allowed to users belonging to the “ADMIN” Role.

Public Key Configuration and JWT settings required in the Application.

Configure the public key needed in the application by using the microprofile config to create a microprofile properties file which would be put in src/main/resources/META-INF/microprofile-config.properties. Next, create a file called keycloak-public-key.pem in the src/main/resources/META-INF folder which will store the RSA public key gotten from the keycloak authorization server.The contents of the microprofile-config.properties is listed below

mp.jwt.verify.publickey.location=/META-INF/keycloak-public-key.pem
mp.jwt.verify.issuer=http://localhost:8084/auth/realms/customerapp

The first line specifies the file location of the public key for the signature verification while the second line tells us about the issuer of the public key.

Leave a Reply

Do NOT follow this link or you will be banned from the site!