Java Posts

Integrating Spring Boot Login with Keycloak or Red Hat Single Sign On

When we are managing many applications, one of the most painful part is managing its user and access right. Because usually different applications have their own user management, and sometimes each user have different credentials between multiple applications.

We can solve this problem by having a one point user management where other application can use this tools for managing their user authentication and authorization. This is where Red Hat Single Sign On (or its opensource product, Keycloak) can comes in handy. It provides an end to end user management lifecyle, from activating a new user, managing them, assigning their access right until deactivating them. On this example, we’ll start with a simple login page by using Keycloak, and how other application (in this example is a Spring Boot app) is connecting to it.

First we need to create a java project with below pom file, im using keycloak-adapter bom and keycloak-spring-boot-starter library for this.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.edw</groupId>
    <artifactId>spring-boot-and-rhsso-otp</artifactId>
    <version>1.0-SNAPSHOT</version>

    <packaging>war</packaging>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.6.RELEASE</version>
        <relativePath/>
    </parent>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.keycloak.bom</groupId>
                <artifactId>keycloak-adapter-bom</artifactId>
                <version>9.0.2</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-spring-boot-starter</artifactId>
            <version>9.0.2</version>
        </dependency>

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

And perhaps the most important part in this project is, the application.properties. In here, we are putting our Keycloak’s url, realm name, client name, and secret. Also we are defining that only user with admin role can access a URL with /admin/ pattern.

### server port
server.port=8080
spring.application.name=Spring Boot with RHSSO Login

### rhsso configuration
keycloak.auth-server-url=https://rhsso/auth/
keycloak.realm=my-realm
keycloak.resource=my-client
keycloak.public-client=false
keycloak.bearer-only=false
keycloak.principal-attribute=preferred_username
keycloak.credentials.secret=11111111-1111-1111-1111-111111111111

### spring boot ui configuration
spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp

### authorization
keycloak.security-constraints[0].authRoles[0]=admin
keycloak.security-constraints[0].securityCollections[0].patterns[0]=/admin/*

Next is we need to create a user and role on Red Hat SSO,

After that, we need to create a client and its password,

And put those corresponding values inside application.properties.

We can test whether configuration works well or not by directly accessing to admin page (/admin/index). A successful configuration would prevent an unauthorized user from accessing admin page by showing a Keycloak login page. Admin page only accessible once a user has successfully login thru Keycloak or Red Hat Single Sign On.

Full code can be downloaded on my github page,

https://github.com/edwin/spring-boot-and-rhsso

Have fun with RHSSO and Keycloak (H)

Google+

Tracing Red Hat Fuse Transaction with Jaeger

Jaeger is open source software for tracing transactions between distributed services. It’s used for monitoring and troubleshooting complex microservices environments. And in this example, im trying trying to use it for tracing http request on Red Hat Fuse, or its opensource version which is Apache Camel. The purpose is to see how much time is needed for each api to take, and what kind of information comes with it.

First lets start by installing Jaeger on your local,

docker pull jaegertracing/all-in-one:1.19

docker run -d --name jaeger -e COLLECTOR_ZIPKIN_HTTP_PORT=9411 \
		-p 5775:5775/udp -p 6831:6831/udp -p 6832:6832/udp -p 5778:5778 \
		-p 16686:16686 -p 14268:14268 -p 14250:14250 -p 9411:9411 \
		jaegertracing/all-in-one:1.19

And create a java project with this pom file,

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.edw</groupId>
    <artifactId>camel-with-jaeger</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <fuse.version>7.2.0.fuse-720020-redhat-00001</fuse.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.jboss.redhat-fuse</groupId>
                <artifactId>fuse-springboot-bom</artifactId>
                <version>${fuse.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.apache.camel</groupId>
            <artifactId>camel-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.camel</groupId>
            <artifactId>camel-http-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.camel</groupId>
            <artifactId>camel-servlet-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-sleuth</artifactId>
            <version>2.1.3.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zipkin</artifactId>
            <version>2.1.3.RELEASE</version>
        </dependency>

    </dependencies>

    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.jboss.redhat-fuse</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <version>${fuse.version}</version>
                </plugin>
            </plugins>
        </pluginManagement>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

Set connection to Jaeger tracing

# The Camel context name
camel.springboot.name=camel-with-jaeger

# enable all management endpoints
endpoints.enabled=true
management.security.enabled=false
camel.component.servlet.mapping.contextPath=/api/*
logging.level.root=info

# name for tracing
spring.application.name=Camel Hello World
spring.zipkin.base-url=http://localhost:9411/
spring.zipkin.enabled=true
spring.zipkin.sender.type=web
spring.sleuth.enabled=true
spring.sleuth.sampler.probability=1.0

Create a simple helo-world api,

<?xml version="1.0" encoding="UTF-8"?>
<rest path="/hello-world" xmlns="http://camel.apache.org/schema/spring">
    <get>
        <route>
            <setHeader headerName="Content-Type">
                <constant>application/json</constant>
            </setHeader>
            <setBody>
                <simple>{ "hello": "world" }</simple>
            </setBody>
        </route>
    </get>
</rest>

We test our newly created api by using curl command,

curl -kv http://localhost:8080/api/hello-world

And the result can be viewed directly on Jaeger dashboard


Now lets say we have other requirement such as adding a specific information on every trace result on Jaeger. We can achieve that by using Tag inside current Trace Span.

package com.edw.routes;

import org.apache.camel.Exchange;
import org.apache.camel.builder.RouteBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.stereotype.Component;

@Component
public class HelloWorldRoute extends RouteBuilder {

    @Autowired
    private Tracer tracer;

    @Override
    public void configure() throws Exception {
        rest()
                .get("hello")
                .route()
                .setHeader(Exchange.HTTP_RESPONSE_CODE, simple("200"))
                .setHeader(Exchange.CONTENT_TYPE, simple("application/json"))
                .process(exchange -> {
                    Span span = tracer.getCurrentSpan();
                    span.tag("something", "whatever");
                    span.tag("pretending to do some queries", "select 1 from dual");
                })
                .setBody(constant("{\"world\":\"world\"}"))
                .endRest()
        ;
    }
}

The result would be displayed on every trace like this,

Full code for this tutorial can be accessed in below url,

https://github.com/edwin/fuse-with-jaeger

Have fun with Jaeger and Camel :)

Google+

Integrating DMN and Business Process on Red Hat Process Automation Manager

DMN stands for Decision Model and Notation. According to Wikipedia it is a standard approach for describing and modeling repeatable decisions within organizations to ensure that decision models are interchangeable across organizations. It is another approach of creating a “decision” on RHPAM (Red Hat Process Automation Manager), other than Decision Table and DRL.

On RHPAM, DMN file can be deployed as a standalone dpeloyment, or as an embedded within a Business Process. On this writing, im trying to do both and we’ll see what are the benefit and weakness of each approach.

Lets try to create a simple DMN to calculate how much loan should one get based on his age and salary. Create “age” and “salary” as DMN Input Data, and two DMN Decisions. “Loan_limit” with Decision Table, and “result” with Context.

Save, Build and Deploy, and we can test it by using rest api. But first we need to check on DMN Namespace and Model Name which is highlighted on the first screenshot and then put is as json parameter.

curl -L -X POST 'http://localhost:8080/kie-server/services/rest/server/containers/loan_validation_1.0.0-SNAPSHOT/dmn' \
-H 'Authorization: Basic cGFtQWRtaW46cGFzc3dvcmQ=' \
-H 'Content-Type: application/json' \
--data-raw '{
  "model-namespace":"https://kiegroup.org/dmn/_43FF885A-C2FB-49A4-BFB4-0F007A2C1C4F",
  "model-name":"Validation",
  "dmn-context": {
    "age":50,
    "salary":1200
  }
}'

The next step is put this DMN into a workflow. We can start by crating a simple workflow, dont forget to add DMN Namespace and Model Name on Business Rule Task.

And run this curl ommand to create a new instance,

curl -L -X POST 'http://localhost:8080/kie-server/services/rest/server/containers/loan_validation_1.0.0-SNAPSHOT/processes/loan_workflow/instances/' \
-H 'Authorization: Basic cGFtQWRtaW46cGFzc3dvcmQ=' \
-H 'Content-Type: application/json' \
--data-raw '{
    "age":50,
    "salary":1200
}'

And we can see the result on log,

12:58:08,486 INFO  [stdout] (default task-17) ================
12:58:08,486 INFO  [stdout] (default task-17) you are eligible for 20000
12:58:08,486 INFO  [stdout] (default task-17) ================

Code sample can be downloaded on below github repository,

https://github.com/edwin/rhpam-loan-validation-sample-with-dmn
Google+

Securing Quarkus Metric API

Usually im creating a metrics API for displaying statistics and various metrics which are being use for measuring application healthiness. There are various tools that being use to capturing this statistics and displaying it, one example is using Prometheus and Grafana.

But on this example, we are not talking too much detail about Prometheus and Grafana, but more on how we providing those metrics on top of Quaskus while securing it so that no malicious user can access this metric API easily.

Lets start with a simple pom file,

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>QuarkusPrometheus</groupId>
    <artifactId>com.edw</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <quarkus.version>1.6.1.Final</quarkus.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <surefire-plugin.version>2.22.1</surefire-plugin.version>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>io.quarkus</groupId>
                <artifactId>quarkus-bom</artifactId>
                <version>${quarkus.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-resteasy</artifactId>
        </dependency>
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-resteasy</artifactId>
        </dependency>

        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-smallrye-metrics</artifactId>
        </dependency>
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-smallrye-health</artifactId>
        </dependency>
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-elytron-security-properties-file</artifactId>
        </dependency>

        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-junit5</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>io.rest-assured</groupId>
            <artifactId>rest-assured</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>${surefire-plugin.version}</version>
                <configuration>
                    <systemProperties>
                        <java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
                    </systemProperties>
                </configuration>
            </plugin>
            <plugin>
                <groupId>io.quarkus</groupId>
                <artifactId>quarkus-maven-plugin</artifactId>
                <version>${quarkus.version}</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>build</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

And a very simple application.properties for storing all my configurations, in here we can see that we are defining a specific role for accessing our metrics endpoint.

#port
quarkus.http.port=8082

#security
quarkus.security.users.embedded.enabled=true
quarkus.security.users.embedded.plain-text=true
quarkus.security.users.embedded.users.admin=password
quarkus.security.users.embedded.roles.admin=prom-role

quarkus.http.auth.policy.prom-policy.roles-allowed=prom-role

quarkus.http.auth.permission.prom-roles.policy=prom-policy
quarkus.http.auth.permission.prom-roles.paths=/metrics

run by using below command,

compile quarkus:dev

Try opening our metrics url directly,

We can try to login by using a specific username and password,

admin / password

If successfully login, we can see this view

One interesting thing is that we can use a different url for Kubernetes’s health and liveness probe check, without have to use any credential at all.

Sourcecode for this example can be downloaded on below url,

https://github.com/edwin/quarkus-secure-prometheus-api

Have fun with Quarkus :-)

Google+

A Simple RHPAM-Application Integration with Multi User Approval Workflow

On this article, im trying to create a simple Leave Approval System which is deployed as microservices. So basically there are two different application, one Golang app as Frontend, and RHPAM (Red Hat Process Automation Manager) as Backend, and both are communicating by using a simple REST API.

Basically there are two users involved here, one is adminUser as requester, and spv02 as approver. As approver, spv02 can either accept or reject the requested leave. Easiest way to described the approval workflow is perhaps described in below image,

Lets start by creating two user on RHPAM,

adminUser / password
spv02 / password

Import project from github (code is at the end of this article), build and deployed it to KIE.

Now lets start with creating an entry form,

The touch-point between frontend and pam on this ui is RHPAM’s “process instances” API, and im adding a correlation-key which is a unique business primary key.

curl -L -X POST 'http://localhost:8080/kie-server/services/rest/server/containers/approval_system_1.0.1-SNAPSHOT/processes/approval/instances/correlation/TL-3422' \
-H 'Authorization: Basic YWRtaW5Vc2VyOnBhc3N3b3Jk' \
-H 'Content-Type: application/json' \
--data-raw '{
    "application": {
        "com.myspace.approval_system.Request": {
            "days": 9,
            "purpose":"Sick Leave"
        }
    }
}'

Next step is displaying all leave request that have been made by this corresponding user, we can capture this by using server queries API, given a specific username as initiator parameter,

curl -L -X GET 'http://localhost:8080/kie-server/services/rest/server/queries/processes/instances?initiator=adminUser&page=0&pageSize=10&sortOrder=true&status=1&status=2&status=3' \
-H 'Accept: application/json' \
-H 'Authorization: Basic YWRtaW5Vc2VyOnBhc3N3b3Jk'

Moving forward, now we are seeing from approval’s point of view, first we need to display what are the tasks which are assign to this user.

curl -L -X GET 'http://localhost:8080/kie-server/services/rest/server/queries/tasks/instances/owners?page=0&pageSize=10&sortOrder=true&sort=taskId' \
-H 'Accept: application/json' \
-H 'Authorization: Basic c3B2MDI6cGFzc3dvcmQ='

And the ability to Approve or Reject a specific request, there are two APIs which are involved here. That is one API for starting the task, and another one to complete it. Make sure you differentiate parameters on “approved” field, use “false” for Rejecting request, and “true” for Accepting request.

curl -L -X PUT 'http://localhost:8080/kie-server/services/rest/server/containers/approval_system_1.0.1-SNAPSHOT/tasks/6/states/started' \
-H 'Authorization: Basic c3B2MDI6cGFzc3dvcmQ='
curl -L -X PUT 'http://localhost:8080/kie-server/services/rest/server/containers/approval_system_1.0.1-SNAPSHOT/tasks/6/states/completed' \
-H 'Authorization: Basic c3B2MDI6cGFzc3dvcmQ=' \
-H 'Content-Type: application/json' \
--data-raw '{
    "approved": false
}'

And here are my repositories on Github,

frontend : 
https://github.com/edwin/frontend-for-approval-rhpam

backend :
https://github.com/edwin/rhpam-simple-approval-concept

Have fun playing with RHPAM :)

Google+