Saturday, April 13, 2019

Securing a Microservice With Keycloak

Hi there!
In this post, I'll be demonstrating the way I secured a microservice with Keycloak. The sample microservice is created using JAX-RS and deployed to wildfly11.




Prerequisites

  1. Keycloak docker image - https://hub.docker.com/r/jboss/keycloak/
  2. Java 7/8 and Maven 3
  3. Wildfly11

The secured microservice project is hosted on GitHub. Please click here to clone the project.

A look at the Microservice

The microservice contains only a single REST endpoint called PERSON API which will return a json message with the application name, name of the person extracted from query parameter and other sample parameters.

PersonAPI.class 


package com.demo.keycloak.service;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.ws.rs.HeaderParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import java.util.HashMap;
import java.util.Map;

@Path("/keycloak-demo")
public class PersonAPI {

    private static Gson gson = new GsonBuilder().create();

    @POST
    @Path("person/{name}")
    public Response start(@PathParam("name") String personName, String requestBody,
                          @HeaderParam("Authorization") String authorization, @Context UriInfo uriInfo) {

        Map<String, Object> reponseMap = new HashMap<>();

        final String path = uriInfo.getAbsolutePath().getPath();
        final String appName = path.substring(1, path.indexOf("-"));
        logger.info("\n\n Application Name : " + appName);

        reponseMap.put("CLIENT_ID", appName);
        reponseMap.put("SUCESS", true);
        reponseMap.put("PERSON_NAME", personName);

        return Response.status(200).entity(convertMapToJSONString(reponseMap)).header("Content-Type", "application/json")
                .build();
    }

    public String convertMapToJSONString(Map<String, Object> responseMap) {
        logger.debug("\n\n convertMapToJSONString :: responseMap :" + responseMap.toString());
        return gson.toJson(responseMap);
    }


    private static final Logger logger = LoggerFactory.getLogger(PersonAPI.class);

}

Securing the REST Endpoint 


Below are the keycloak maven dependencies needed. 


    <!-- keycloak jars -->

        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-core</artifactId>
            <version>${keycloak-version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-common</artifactId>
            <version>${keycloak-version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-authz-client</artifactId>
            <version>${keycloak-version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-adapter-core</artifactId>
            <version>${keycloak-version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-adapter-spi</artifactId>
            <version>${keycloak-version}</version>
            <scope>provided</scope>
        </dependency>

Now, we need to secure {{hostname}}/keycloak-demo-service/keycloakDemoService/keycloak-demo/person/ashen by adding constraints and roles to web.xml file.

web.xml


<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" metadata-complete="false" version="3.0">
  <display-name>Restful Web Application</display-name>
  <context-param>
    <param-name>resteasy.scan</param-name>
    <param-value>true</param-value>
  </context-param>
  <context-param>
    <param-name>resteasy.servlet.mapping.prefix</param-name>
    <param-value>/keycloakDemoService</param-value>
  </context-param>

  <!-- keycloak -->

  <context-param>
    <param-name>keycloak.config.resolver</param-name>
    <param-value>com.demo.keycloak.auth.KeycloakAuthResolver</param-value>
  </context-param>

  <security-constraint>
    <web-resource-collection>
      <web-resource-name>REST endpoints</web-resource-name>
      <url-pattern>/keycloakDemoService/keycloak-demo/person/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
      <role-name>personAPI</role-name>
    </auth-constraint>
  </security-constraint>


  <login-config>
    <auth-method>KEYCLOAK</auth-method>
    <realm-name>JavaSecurity</realm-name>
  </login-config>

  <security-role>
    <role-name>personAPI</role-name>
  </security-role>

  <!--RestEasy-servlet-->

  <listener>
    <listener-class>
      org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap</listener-class>
  </listener>

  <servlet>
    <servlet-name>resteasy-servlet</servlet-name>
    <servlet-class>
      org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher</servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>resteasy-servlet</servlet-name>
    <url-pattern>/keycloakDemoService/*</url-pattern>
  </servlet-mapping>

</web-app>

Above snippet configures  the Undertow (Undertow is the Web Server on Wildfly ) to require the users to have certain roles to be allowed to invoke the person endpoint. In this case the person endpoint requires the role personAPI.

Creating a Realm in Keycloak


What is a realm in Keycloak?

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.

Create a realm called 'JavaSecurity' 

Creating a Client in Keycloak


Now we need to create a client for the microservice within Keycloak. The client name will be 'keycloak-demo'. Open Keycloak Admin Console in your browser. http://localhost:8080/auth/
Sign in and click on Clients in the menu on the left hand side. Once that's open, click on Create on top of the table and you will navigated to a page as shown below.











Click on Save and you will redirected to a page as shown in below screenshot.  Change the access type to public.
Access Type : Public
Public access is for client side clients that need to perform a browser login.





















Now click on the installation tab on top of the form. Select Keycloak OIDC JSON. This should provide a json object as below with required configuration for the keycloak client adapter.


{
  "realm": "JavaSecurity",
  "realm-public-key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Oj1K8EHWyAkaggvteHnETu3acwn5U8PaU0BixBM1nke5cgNXWE3I5M4zDfmNG2Egub2jmMHyRbF9U10ovQzhE1HhDFN+3ob/+Wz8Dy0ZF6GXAzyWYe64vx1MiDq5764HNQGAv+zSlUD0S7TIbCrw3h9XBJQNqeJsOurpIVu2+M0wxXXOylmIaCVFqf509rMPcdmS+UocZ7+2mAC7eJ5z4u6j5rsBvSK74tKQb7tcJHXcY3TO6owKayUVrdDHnruYhQxQu1x0FeemDrtvv6D7O2FhhOWIBJcvgLyuuyFYH687o/xxaD9Ye+P9SP1NYCbRmlkVQJ3DPs6ibLQkaDr4QIDAQAB",
  "auth-server-url": "http://localhost:8080/auth",
  "ssl-required": "external",
  "resource": "keycloak-demo",
  "public-client": true,
  "use-resource-role-mappings": true
}

This json object is copied to the keycloak.json file inside resources folder. The linking between the keycloak adapter and the keycloak.json happens from KeycloakAuthResolver class. This class overrides the Keycloak config resolver which enables to reside the keycloak.json file externally and provide the path in the standalone.xml file. If the external keycloak.json file is not available, by default it will look into the keycloak,json file inside resources folder.

Creating User and Roles


Create a role called "PersonAPI" under keycloak-demo client as shown below. 








Create a user as shown below.




















Assign the "PersonAPI" role to the created user.















Get the access token




Paste the access token to the Authorization header of the Person request as shown below. If the access token is invalid, unauthorized error message will be received.



if valid, below response will be received.







!...Happy Coding...!

Ashen Jayasinghe
DMS
Full Stack Developer

Sunday, March 24, 2019

Refactoring a complex Java EE Monolithic App to Microservices

Hi there! In this blog post, I have addressed some of the major questions that will arise when transforming a Java EE monolithic application to a microservice architecture from my own experience.

I have been working at the final phase of a Java EE monolithic telecommunication application. According to the client requirements, after the final phase, we should break down the functionalities of the monolith application which is packaged on a single WAR archive to multiple micro services. ( multiple JARs and WARs ). Initially, I was assigned for a research task on microservices to identify the best practices, technologies and methods for breaking down the monolith application.

Below are the identified steps from the research in order to migrate the monolith application to microservices.

1. Follow Domain driven design architecture when creating micro-services or decomposing the existing monolith application. (Sub domains, boundary contexts, context maps)
2. Repackage the application
  • Repackage the app into multiple individual WAR files and apply container-per-service pattern and deploy each WAR file separately in to Wildfly or Docker container.
  • Build, deploy, and manage independently: After the WAR files are split, you can manage each WAR file independently through an automated DevOps pipeline.

3. Refactoring the application code.

Existing REST or JMS services ; You might have services that are already compatible with a microservices architecture, or that can be made compatible. Start by untangling each REST or simple JMS service from the rest of the WAR file, and then deploy each service as its own WAR file. At this level, duplication of supporting JAR files is fine—this is still mostly a question of packaging.

Existing EJB services : If you have services, they were probably built by following a functional approach, such as the Service Façade pattern. In this case, you can usually refactor function-based services design into an asset-based services design. If the functions in the Service Façade were written as create, retrieve, update, and delete operations on a single object, the mapping to a RESTful interface is simple: reimplement the EJB session bean interface or JAX-WS interface as a JAX-RS interface.

Internal Structure of a microservice
Calls to external services should be separate from the domain logic. Below figure shows a simplified version of a microservice architecture. This architecture separates the domain logic from any code that interacts with or understands external services. The architecture is similar to the Ports and Adapters (Hexagonal) architecture

Resources expose JAX-RS resources to external clients. This layer handles basic validation of requests and then passes the information into the domain logic layer.

Domain logic, as a general concept, takes many forms. In Boundary-Entity-Control patterns, domain logic represents the entity itself, with parameter validation, state change logic, and so on.

Repositories are optional, but can provide a useful abstraction between the core application domain logic and the data store when present. This configuration allows the backing data store to be changed or replaced without extensive changes to the domain logic.

Service connectors are similar to the Repository abstraction, encapsulating communications with other services. This layer functions as either a façade or an “anti-corruption” layer to protect domain logic from changes to external resource APIs, or to convert between API wire formats and internal domain model constructs.

http://alistair.cockburn.us/Hexagonal+architecture
http://martinfowler.com/articles/microservice-testing/#anatomy-modul


4. Refactoring the data.
  • Isolated islands of data
Begin by looking at the database tables that your code uses. If the tables are either independent of all other tables or come in a small, isolated island of a few tables that are joined by relationships, you can split the tables from the rest of your data design. Then you can consider the right option for your service. For instance, do you stay in SQL, but consider moving from a heavyweight enterprise database such as Oracle to a smaller, self-contained database like MySQL? Or do you consider a NoSQL database to replace your SQL database?

The answer to that question depends on the kinds of queries you run on your data. If most of the queries are simple queries on "primary" keys, a key-value database or a document database might serve you well. Alternatively, if you have complex joins that vary widely—for example, your queries are unpredictable—staying with SQL might be your best option.

  • Batch data updates 
If you have only a few relationships and you decide to move your data into a NoSQL database anyway, consider a batch update into your existing database. When you consider the relationships between tables, the relationships often don't take a time factor into consideration. They might not need to be always up to date—a data dump/load approach that runs every few hours might be fine for many cases.

  • Table denormalization
If you have more than a few relationships to other tables, you might be able to refactor, or in database administrator (DBA) terms, "denormalize," your tables. Even the discussion of this idea might alarm a DBA. However, your team as a whole should think about why data was normalized to begin with. Often, the reason for highly normalized schemas was to reduce duplication, which was to save space, because disk space was expensive. However, that's not true anymore. Instead, query time is now the factor you want to optimize. Denormalization is a straightforward way to achieve that.

https://www.ibm.com/cloud/garage/architectures/microservices/refactor-to-microservices


5.  Inter-service-communication - Use Saga Pattern (Event driven architecture)

Use Saga Pattern to maintain data consistency / usability across services if each service has its own database. 

This pattern has the following benefits
  • It enables an application to maintain data consistency across multiple services without using distributed transactions .
This solution has the following drawbacks:
  • The programming model is more complex. For example, a developer must design compensating transactions that explicitly undo changes made earlier in a saga. 
There are also the following issues to address:
  • In order to be reliable, a service must atomically update its database and publish an event. It cannot use the traditional mechanism of a distributed transaction that spans the database and the message broker. Instead, it must use one of the patterns listed below.

Microservices maturity model




References


https://microservices.io/patterns/microservice-chassis.html
https://www.nginx.com/blog/refactoring-a-monolith-into-microservices/
https://stackoverflow.com/questions/30286443/microservices-how-to-store-source-code-of-many-microservices
https://microservices.io/patterns/observability/health-check-api.html
http://projects.spring.io/spring-cloud/
https://microservices.io/patterns/observability/distributed-tracing.html
https://martinfowler.com/bliki/CircuitBreaker.html
https://dzone.com/refcardz/getting-started-with-microservices?chapter=5
http://callistaenterprise.se/blogg/teknik/2015/03/25/an-operations-model-for-microservices/
http://callistaenterprise.se/blogg/teknik/2017/05/12/building-microservices-part-6-configuration-server/
http://callistaenterprise.se/blogg/teknik/2017/09/13/building-microservices-part-8-logging-with-ELK/
http://callistaenterprise.se/blogg/teknik/2015/04/10/building-microservices-with-spring-cloud-and-netflix-oss-part-1/
https://www.infoq.com/articles/microservices-aggregates-events-cqrs-part-1-richardson