link

# Java electronic signatures quick start guide

# Introduction

If you want to skip the setup and start testing electronic signatures right away, you can download and use a sample project. Otherwise, you can also set up your own sample project.

Note

This guide works with multiple Java versions, but is written for and tested with Java 8.

# Set up sample project

# 1. Prerequisites

Install Java and Maven (opens new window).

# 2. Download the sample

Download sample project

# 3. Build the project

mvn package

# 4. Run the sample project

java -jar target/signapi-demo-1.0-SNAPSHOT-jar-with-dependencies.jar

Using an IDE?

Execute the Maven goal openapi-generator:generate through your IDE or command line and mark target/generated-sources/src/main/java as a Generated Sources root

You will get a URL to access the signing order. Click on it and sign it with a test user (see below). Don't use your own!

Test user credentials for Norwegian BankID

  • User ID: 10103933108
  • One-time password: otp
  • Password: qwer1234

The signed document will be automatically downloaded to the folder you ran the project from. The process is now complete.

# Sample project walkthrough

Following is a walkthrough, step by step, of how the sample project is set up.

Signicat's Sign API is based on the OpenAPI 3.0 specification (opens new window), exposing an always up-to-date API specification schema and enables the use of code generation tools to easily generate API clients.

The OpenAPI Specification schema for Sign API in production can be found at https://id.signicat.com/sign/openapi.json.

This setup has three main parts:

# Before you start

# Create the project

  1. In Maven, generate a new project for the demo application:
mvn archetype:generate \
-DarchetypeGroupId=org.apache.maven.archetypes \
-DarchetypeArtifactId=maven-archetype-quickstart \
-DarchetypeVersion=1.4 \
-DgroupId=com.example.demo \
-DartifactId=signapi-demo \
-Dversion=1.0-SNAPSHOT \
-Dpackage=com.example.demo.signapi
  1. Create a resources directory in the new project:
mkdir signapi-demo/src/main/resources
  1. Download the OpenAPI specification file (opens new window) from our pre-production environment and place it in the resources directory:
cURL https://preprod.signicat.com/sign/openapi.json -o signapi-demo/src/main/resources/signapi-spec.json

Our pre-production environment is designed to be as "prod-like" as possible, running the same application versions, but using test accounts instead of live accounts towards the third-party identity methods. In the case of the Sign API, the only difference between the pre-production and production OpenAPI schemas should be the servers array element.

To generate the code to interact with the API, we will use the openapi-generator-maven-plugin from the OpenAPI Generator project (opens new window). The OpenAPI generator supports generating clients in a number of different languages and frameworks. For this example, we will use the Jersey2 and Jackson libraries for our HTTP client and JSON serialisation.

  1. Add the necessary dependencies to the <dependencies> section in pom.xml:
<!-- dependencies are needed for the client being generated -->
    <dependency>
      <groupId>io.swagger</groupId>
      <artifactId>swagger-annotations</artifactId>
      <version>${swagger-annotations-version}</version>
    </dependency>
            
    <!-- HTTP client: jersey-client -->
    <dependency>
        <groupId>org.glassfish.jersey.core</groupId>
        <artifactId>jersey-client</artifactId>
        <version>${jersey-version}</version>
    </dependency>
    <dependency>
        <groupId>org.glassfish.jersey.media</groupId>
        <artifactId>jersey-media-multipart</artifactId>
        <version>${jersey-version}</version>
    </dependency>
    <dependency>
        <groupId>org.glassfish.jersey.media</groupId>
        <artifactId>jersey-media-json-jackson</artifactId>
        <version>${jersey-version}</version>
    </dependency>
    <dependency>
            <groupId>org.glassfish.jersey.inject</groupId>
            <artifactId>jersey-hk2</artifactId>
            <version>${jersey-version}</version>
        </dependency>

    <!-- @Nullable annotation -->
    <dependency>
        <groupId>com.google.code.findbugs</groupId>
        <artifactId>jsr305</artifactId>
        <version>3.0.2</version>
    </dependency>

    <!-- JSON processing: jackson -->
    <dependency>
        <groupId>com.fasterxml.jackson.jaxrs</groupId>
        <artifactId>jackson-jaxrs-base</artifactId>
        <version>${jackson-version}</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-core</artifactId>
      <version>${jackson-version}</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-annotations</artifactId>
      <version>${jackson-version}</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>${jackson-version}</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.jaxrs</groupId>
      <artifactId>jackson-jaxrs-json-provider</artifactId>
      <version>${jackson-version}</version>
    </dependency>
    <dependency>
        <groupId>org.openapitools</groupId>
        <artifactId>jackson-databind-nullable</artifactId>
        <version>${jackson-databind-nullable-version}</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.datatype</groupId>
        <artifactId>jackson-datatype-jsr310</artifactId>
        <version>${jackson-version}</version>
    </dependency>
  1. Specify the versions we want in the <properties> section:
<swagger-annotations-version>1.5.8</swagger-annotations-version>
<jersey-version>2.27</jersey-version>
<jackson-version>2.8.9</jackson-version>
<jackson-databind-nullable-version>0.2.0</jackson-databind-nullable-version>
  1. Since client generation with OAuth support is still in development by the OpenAPI Generator project, we will add one more dependency to handle the authentication for us:
<!-- ScribeJava -->
<dependency>
    <groupId>com.github.scribejava</groupId>
    <artifactId>scribejava-core</artifactId>
    <version>6.8.1</version>
</dependency>
  1. With all the dependencies in place, we're now ready to add the code generator plugin and a plugin to generate a fat JAR for the application. In the <build> section of pom.xml (outside the <pluginManagement> element), add the following snippet:
<plugins>
    <plugin>
        <groupId>org.openapitools</groupId>
        <artifactId>openapi-generator-maven-plugin</artifactId>
        <!-- RELEASE_VERSION -->
        <version>4.2.3</version>
        <!-- /RELEASE_VERSION -->
        <executions>
            <execution>
                <goals>
                    <goal>generate</goal>
                </goals>
                <configuration>
                <inputSpec>${project.basedir}/src/main/resources/signapi-spec.json</inputSpec>
                <language>java</language>
                <library>jersey2</library>
                <configOptions>
                    <sourceFolder>src/main/java</sourceFolder>
                    <dateLibrary>java8</dateLibrary>
                    <java8>true</java8>
                </configOptions>
                <output>${project.build.directory}/generated-sources</output>
                <modelPackage>com.example.demo.signapi.client.model</modelPackage>
                <apiPackage>com.example.demo.signapi.client.api</apiPackage>
                <invokerPackage>com.example.demo.signapi.client.invoker</invokerPackage>

                <generateApis>true</generateApis>
                <generateApiTests>false</generateApiTests>
                <generateApiDocumentation>false</generateApiDocumentation>

                <generateModels>true</generateModels>
                <generateModelTests>false</generateModelTests>
                <generateModelDocumentation>false</generateModelDocumentation>

                <generateSupportingFiles>true</generateSupportingFiles>
            </configuration>
            </execution>
        </executions>
    </plugin>

    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-assembly-plugin</artifactId>
        <version>3.1.1</version>
        <configuration>
            <descriptorRefs>
                <descriptorRef>jar-with-dependencies</descriptorRef>
            </descriptorRefs>
            <archive>
                <manifest>
                    <mainClass>com.example.demo.signapi.App</mainClass>
                </manifest>
            </archive>
        </configuration>
        <executions>
            <execution>
                <id>make-assembly</id>
                <phase>package</phase>
                <goals>
                    <goal>single</goal>
                </goals>
            </execution>
        </executions>
    </plugin>
</plugins>
  1. Now, run mvn clean compile to generate all the classes necessary for interacting with the Sign API, which will make them available in the demo application.

# Demo the signing process

Now that the project is created, let's use the generated classes for a simple signing scenario where we:

  • Upload a document we want to sign
  • Create a signing order to sign the document using Norwegian BankID
  • Sign the document
  • Download the signed document
  1. First of all, let's import the packages we'll need by adding package com.example.demo.signapi; in App.java:
import com.example.demo.signapi.client.api.DocumentApi;
import com.example.demo.signapi.client.api.SigningOrderApi;
import com.example.demo.signapi.client.invoker.ApiClient;
import com.example.demo.signapi.client.invoker.ApiException;
import com.example.demo.signapi.client.model.Document;
import com.example.demo.signapi.client.model.DocumentRefId;
import com.example.demo.signapi.client.model.Method;
import com.example.demo.signapi.client.model.SigningOrder;
import com.example.demo.signapi.client.model.SigningOrderRefId;
import com.example.demo.signapi.client.model.Task;
import com.example.demo.signapi.client.model.TaskStatusInfo;
import com.github.scribejava.core.builder.ServiceBuilder;
import com.github.scribejava.core.builder.api.DefaultApi20;
import com.github.scribejava.core.oauth.OAuth20Service;

import java.io.File;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.UUID;
  1. Then, place the document you want to sign in src/main/resources/document.pdf and add the following method to App.java
private static String uploadDocument(DocumentApi documentApi) throws ApiException {
        // Read a local file to upload
        File file = new File("src/main/resources/document.pdf");

        // Use DocumentApi to perform HTTP POST to /documents endpoint
        DocumentRefId documentRefId = documentApi.uploadFileToSds(file);
        return documentRefId.getDocumentId();
}
  1. We'll also create a method that creates a Signing Order referencing the document we just uploaded:
private static String createSigningOrder(SigningOrderApi signingOrderApi, String documentRefId, String taskId) throws Exception {
        // Create signing order model
        SigningOrder signingOrder = new SigningOrder();

        Task task = new Task();
        task.setId(taskId);

        Document document = new Document();
        document.setId("doc-1");
        document.setDocumentRef(documentRefId);
        document.setDescription("Letter of intent");
        document.setSource(Document.SourceEnum.SESSION);
        document.setAction(Document.ActionEnum.SIGN);

        task.getDocuments().add(document);

        // Use Norwegian BankID as our signing method
        Method method = new Method();
        method.setName("nbid-sign");
        method.setType(Method.TypeEnum.SIGNED_STATEMENT);

        task.getSignatureMethods().add(method);

        signingOrder.getTasks().add(task);

        // POST to /orders
        SigningOrderRefId signingOrderRefId = signingOrderApi.createRequest(signingOrder);
        return signingOrderRefId.getId();
}
  1. Let's also create a little helper method to fetch an Access Token that let's us access the API:
private static String getAccessToken() throws Exception {
        // Set up OAuth client using ScribeJava and fetch access token
        String clientId = "test.demo.microservices";
        String clientSecret = "BVn1suS2TkKCMWejcTmxgW-Dmuul0wKSZ59liKa0bW4";

        OAuth20Service service = new ServiceBuilder(clientId)
                .apiSecret(clientSecret)
                .defaultScope("client.signature")
                .build(new DefaultApi20() {

                    @Override
                    public String getAccessTokenEndpoint() {
                        return "https://preprod.signicat.com/oidc/token";
                    }

                    @Override
                    protected String getAuthorizationBaseUrl() {
                        return null;
                    }
                });
        
        return service.getAccessTokenClientCredentialsGrant().getAccessToken();
}
  1. Now we can put it all together in our main method, where we execute the steps of our signing scenario:
public static void main(String[] args) throws Exception {
        
        // Get a valid access token
        String accessToken = getAccessToken();

        // Set up our ApiClient to use the access token
        ApiClient apiClient = new ApiClient();
        apiClient.setBasePath("https://preprod.signicat.com/sign/");
        apiClient.setAccessToken(accessToken);

        // Set up DocumentApi and SigningOrderApi to use the ApiClient
        DocumentApi documentApi = new DocumentApi(apiClient);
        SigningOrderApi signingOrderApi = new SigningOrderApi(apiClient);

        String documentRefId = uploadDocument(documentApi);

        String taskId = UUID.randomUUID().toString();
        String orderId = createSigningOrder(signingOrderApi, documentRefId, taskId);

        String signingUrl = String.format("https://preprod.signicat.com/std/docaction/demo?request_id=%s&task_id=%s", orderId, taskId);

        System.out.println("Signing order created, sign the document at " + signingUrl);
        System.out.println("Waiting for signing to be completed");

        // Naively poll task status until the task has been completed.
        // In your real implementation, you can use URL notifications to have Signicat send a request to your system
        // when a task changes status instead of this polling.
        TaskStatusInfo.TaskStatusEnum taskStatus = null;
        while (taskStatus != TaskStatusInfo.TaskStatusEnum.COMPLETED) {
            Thread.sleep(5000);
            taskStatus = signingOrderApi.getStatus(taskId, orderId).getTaskStatus();
            System.out.println("Status: " + taskStatus);
        }
        
        // Task has been completed, download the resulting LTV-SDO as result.xml
        File tmpResult = signingOrderApi.getResultDocument("doc-1", taskId, orderId);
        Files.copy(tmpResult.toPath(), new File("result.xml").toPath(), StandardCopyOption.REPLACE_EXISTING);
        System.out.println("Downloaded result document as result.xml");
}
  1. Build and run the application:
mvn package
java -jar target/signapi-demo-1.0-SNAPSHOT-jar-with-dependencies.jar
  1. Sign the document using the URL in the response. Use test credentials for Norwegian BankID, never your own!

Test user credentials for Norwegian BankID

  • User ID: 10103933108
  • One-time password: otp
  • Password: qwer1234

You should now see the result of the signing process downloaded as result.xml.

Last updated: 8/17/2021, 4:55:21 PM