# Java electronic signing quick start guide
If you want to skip the setup and start testing electronic signing 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
# 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
- This guide works with multiple Java versions but is written for Java8.
- Maven needs to be installed. Click here to install Maven. (opens new window)
# Create the project
- 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
- Create a
resources
directory in the new project:
mkdir signapi-demo/src/main/resources
- Download the OpenAPI specification file (opens new window) from our preproduction 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 preproduction 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 preproduction 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 serialization.
- Add the necessary dependencies to the
<dependencies>
section inpom.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>
- 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>
- 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>
- 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 ofpom.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>
- 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
- First of all, let's import the packages we'll need by adding
package com.example.demo.signapi;
inApp.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;
- Then, place the document you want to sign in
src/main/resources/document.pdf
and add the following method toApp.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();
}
- 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();
}
- 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();
}
- 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");
}
- Build and run the application:
mvn package
java -jar target/signapi-demo-1.0-SNAPSHOT-jar-with-dependencies.jar
- 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
.