1. Introduction

This tutorial describes how to create a Java client that can log in to the yuuvis® Momentum API.

We will create a Maven project as basis for further interaction with the yuuvis® Momentum system.

2. Requirements

You need the knowledge and setup from the tutorial Installing a Minimal System.

To work through this tutorial, the following is required:

  • Simple Maven project

  • Java (we tested with Java 21)

3. Maven Configuration

In the tutorial, the OkHttpClient by Square, Inc. is used. Therefore, the first two dependencies in the following block must be added to the Maven dependencies in the pom.xml of the project. The third and forth dependencies are required for the handling of JSON request and response bodies that are expected/provided by the API.

Dependencies in the 'pom.xml' file
<dependencies>
    <dependency>
        <groupId>com.squareup.okhttp3</groupId>
        <artifactId>okhttp</artifactId>
        <version>3.12.0</version>
    </dependency>
    <dependency>
        <groupId>com.squareup.okhttp3</groupId>
        <artifactId>okhttp-urlconnection</artifactId>
        <version>3.12.0</version>
    </dependency>

    <dependency>
        <groupId>com.squareup.okio</groupId>
        <artifactId>okio</artifactId>
        <version>3.16.4</version>
    </dependency>
    <dependency>
        <groupId>org.json</groupId>
        <artifactId>json</artifactId>
        <version>20250517</version>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
        </plugin>
    </plugins>
</build>

We need to enable the usage of HTTP in our local Maven settings as only HTTPS is allowed in the Maven default configuration of version 3.8.1 and later. In our project, we create a .mvn directory with a local-setings.xml file.

Project-specific Maven settings '.mvn/local-settings.xml'
<settings xmlns="http://maven.apache.org/SETTINGS/1.2.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.2.0 http://maven.apache.org/xsd/settings-1.2.0.xsd">

    <mirrors>
        <mirror>
            <id>release-http-unblocker</id>
            <mirrorOf>central</mirrorOf>
            <name></name>
            <url>http://my-url/libs-release</url>
        </mirror>
        <mirror>
            <id>snapshot-http-unblocker</id>
            <mirrorOf>snapshots</mirrorOf>
            <name></name>
            <url>http://my-url/libs-snapshot</url>
        </mirror>
    </mirrors>
</settings>

4. Imports

We import the following Java libraries to use them in our application.

Used Java Libraries
import okhttp3.*;

import java.net.CookieManager;
import java.net.CookiePolicy;
import java.util.Base64;

5. Login Data

To use our Java client, we need the login information. We store them in variables such that we can reuse the values later.

Login information stored in variables
public static final String username = "root";
public static final String userpassword = "changeme";
public static final String auth = "Basic "+ Base64.getEncoder().encodeToString((username+":"+userpassword).getBytes());
public static final String tenant = "myfirsttenant";
public static final String baseUrl = "http://123.456.78.9:30080";

6. Session Management

The login is done using request headers, which are passed during the first call of an API function. The calling HTTP client must have a Cookie Manager to enrich further requests with the session cookie (GWSESSIONID). This prevents the user from logging on again for each request. To activate Cookie Management in the OkHttpClient, you can do the following:

Activate Cookie Management in OkHttpClient
try {
    CookieJar cookieJar = new JavaNetCookieJar(new CookieManager(null, CookiePolicy.ACCEPT_ALL));
    OkHttpClient client = new OkHttpClient.Builder().cookieJar(cookieJar).build();

} catch (Exception e) {
    e.printStackTrace();

}

Please note that an IOException can be thrown by the OkHttpClient. That’s why we use it always wrapped with try and catch.

7. Testing

We can now easily test the login by calling a simple API endpoint. For example, we can retrieve the information on the currently running yuuvis® Momentum version.

Call the version info endpoint
Request getVersionRequest = new Request.Builder()
                .header("Authorization", auth)
                .header("X-ID-TENANT-NAME", tenant)
                .url(baseUrl + "/api/dms/info")
                .build();
Response getVersionResponse = client.newCall(getVersionRequest).execute();
System.out.println(getVersionResponse.body().string());

8. Secure Data Transmission with SSL

To use encrypted (HTTPS) endpoints with an OkHttpClient, it must be configured for the SSL encryption protocol. For this, it must also be capable of certificate treatment according to the X-509 standard. For this, the OkHttpClient receives a hostnameVerifier and a sslSocketFactory at initialization to check incoming certificates with the TLS handshake. In our example, we just keep building our X509TrustManager for the sslSocketFactory for demonstration purposes, so that certificates are almost always accepted. The following code creates an OkHttpClient that is both session- and SSL-enabled.

OkHttpClient with Session Handling and SSL
private OkHttpClient client = null;

// necessary to obtain access tokens via SSL
X509TrustManager trustManager = new X509TrustManager()
{
public void checkClientTrusted(X509Certificate[] x509Certificates, String s) {}
public void checkServerTrusted(X509Certificate[] x509Certificates, String s) {}
public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; }
};


CookieJar cookieJar = new JavaNetCookieJar(new CookieManager(null, CookiePolicy.ACCEPT_ALL));

SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, new TrustManager[]{this.trustManager}, new SecureRandom());

// create HTTP Client
this.client = new OkHttpClient.Builder()
.cookieJar(cookieJar)
.hostnameVerifier((s, sslSession) -> true)
.sslSocketFactory(sslContext.getSocketFactory(), this.trustManager)
.build();

9. Required System Data

To log in to the Core API, a client needs some information about the system. Of course, they first need valid credentials (username / password) of a user existing in the system. In addition, the Keycloak login client must be able to identify itself as an approved application, so it must know a registered clientId with an associated clientSecret. In addition, both pairs of credentials are only valid for a certain tenant whose name must also be known to the client for the requests. Finally, the client must also know the one accessible base URL of the Core API and the associated Keycloak identity provider.

Variables for the Login
private String userName = "clouduser";
private String userPwd = "secret";
private String userTenant = "default";
private String clientId = "clouduser";
private String clientSecret = "cd7e6ce4-781b-40db-af5f-e4106926e96c";  //client secret, obtainable through keycloak credentials tab of client matching client id and tenant
private String yuuvisBaseUrl= "http://10.10.6.242";             //Base URL of yuuvisclient
private String keycloakBaseUrl = "https://10.10.6.252:8443";   //Base URL of keycloak identity provider

9.1. Generating a Secret

To log in with an oAuth2 access token, the client must authenticate itself to Keycloak with its client secret. This secret is static and can be taken from the Keycloak interface. To do this, log on to the Keycloak interface with a technical user, select the tenant applicable to the client, select the correct client under "Clients" (here "Enaio") and take out the secret of the client under the "Credentials" tab.

10. Login Procedures

10.1. Login with Username and Password

The username, password, and associated tenant are passed as HTTP headers. The username and password must be encoded with Base64 in the same way as for classic Basic authentication. The headers are then sent to any Core API endpoint.

Login with Username and Password
byte[] credentials = "clouduser:secret".getBytes(StandardCharsets.UTF_8);
String authorization = "Basic " + Base64.getEncoder().encodeToString(credentials);

Headers headers = new Headers.Builder()
.add("Authorization", authorization)
.add("X-ID-TENANT-NAME", "default")
.build();

10.2. Login with an OAuth 2.0 Access Token

For this login procedure, the application must have received an access token from the identity provider from a third-party application (for example, a proxy). The access token and the associated tenant are passed as HTTP headers for login.

Keycloak can request an access token with the Password Credentials Grant Flow. The request must contain the user name, the user password, the client ID and the secret as parameters. Keycloak’s response contains a JSON object from which the access token and token type must be extracted. In the example, JsonPath is used for this.

Login with an OAuth 2.0 Access Token
String payload = "client_id=enaio&" +
"client_secret=4c5254363c1d&" +
"username=clouduser&" +
"password=secret&" +
"grant_type=password";

//retrieve access token from identity provider (Keycloak)
Request.Builder request = new Request.Builder()
.url(keycloakBaseUrl+"/auth/realms/default/protocol/openid-connect/token")
.post(RequestBody.create(MediaType.parse("application/x-www-form-urlencoded"), payload))
.build();

String responseJson = this.client.newCall(request).execute().body().string();

DocumentContext context = JsonPath.parse(responseJson);
String tokenType = context.read("token_type");
String accessToken = context.read("access_token");

Finally, the logon headers are created and sent to any Core API endpoint.

Creating Headers
Headers headers = new Headers.Builder()
.add("Authorization", tokenType + " " + accessToken)
.add("X-ID-TENANT-NAME", "default")
.build();

10.3. Login with the OAuth 2.0 Device Flow

With this logon procedure, the client application calls the system browser so that the user can log on to an HTML form. The OAuth 2.0 Device Flow is used for this purpose. The procedure is done in the following four steps:

  • Start the login process and read out the parameters Device Code, User Code, and Verification URI. The example uses JsonPath for this. The parameter values lose their validity after five minutes. In this time window, the user must have finished logging in to the browser.

    OAuth 2.0 Device Flow: Step 1
    Request.Builder startRequest = new Request.Builder().url(keycloakBaseUrl+"/tenant/default/loginDevice");
    
    String responseJson = this.client.newCall(startRequest.build()).execute().body().string();
    DocumentContext context = JsonPath.parse(responseJson);
    
    String deviceCode = context.read("device_code");
    String userCode = context.read("user_code");
    String verificationUri = context.read("verification_uri");
    
  • The Verification URI is opened with the User Code as a parameter in the system browser. The Authentication service then forwards to the login page of the identity provider, where the user can log in.

    OAuth 2.0 Device Flow: Step 2
    Desktop.getDesktop().browse(new URI(keycloakBaseUrl + verificationUri + "?user_code=" + userCode));
    
  • At regular intervals, the client application queries the Authentication service to see whether the user has successfully logged in. This is the case if the status request receives the return code OK (200). The response of the status request contains a JSON object from which the access token and the token type must be extracted. In the example, JsonPath is used for this.

    OAuth 2.0 Device Flow: Step 3
    String tokenType = null;
    String accessToken = null;
    
    for (int i = 1 ; i != 30 ; i++, Thread.sleep(2000))
    {
        Request.Builder pollingRequest = new Request.Builder().url(keycloakBaseUrl+"/auth/info/state?device_code=" + deviceCode);
    Response pollingResponse = this.client.newCall(pollingRequest.build()).execute();
    
        if (pollingResponse.code() != 200) continue;
    
        context = JsonPath.parse(pollingResponse.body().string());
        tokenType = context.read("token_type");
        accessToken = context.read("access_token");
    
        break;
    }
    
  • Finally, the logon headers are assembled and sent to any Core API endpoint.

    OAuth 2.0 Device Flow: Step 4
    Headers headers = new Headers.Builder()
    .add("Authorization", tokenType + " " + accessToken)
    .add("X-ID-TENANT-NAME", "default")
    .build();
    

11. Logout

After all necessary API operations have been performed, the session established by the logon can be closed.

Log Out of the Core API
Request.Builder logoutRequest = new Request.Builder().url(keycloakBaseUrl+"/logout");
this.client.newCall(logoutRequest.build()).execute();

12. Summary

This tutorial explains how an OkHttp3 Java client implements the different logon procedures for the Core API.