Skip to content

Instantly share code, notes, and snippets.

@fschutte
Last active October 4, 2020 06:36
Show Gist options
  • Save fschutte/18ad4cf4c37d6c57231b4f560ad29a0f to your computer and use it in GitHub Desktop.
Save fschutte/18ad4cf4c37d6c57231b4f560ad29a0f to your computer and use it in GitHub Desktop.
Simple example for calling ING Bank Sandbox API
package nl.brachio.ingapi;
import com.jayway.jsonpath.JsonPath;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.net.JksOptions;
import io.vertx.ext.web.client.WebClientOptions;
import io.vertx.rxjava.core.Vertx;
import io.vertx.rxjava.core.buffer.Buffer;
import io.vertx.rxjava.ext.web.client.HttpResponse;
import io.vertx.rxjava.ext.web.client.WebClient;
import io.vertx.rxjava.ext.web.codec.BodyCodec;
import rx.Single;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Base64;
import java.util.List;
import java.util.Locale;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
// Dependencies
// <dependencyManagement>
// <dependencies>
// <dependency>
// <groupId>io.vertx</groupId>
// <artifactId>vertx-stack-depchain</artifactId>
// <version>${vertx.version}</version>
// <type>pom</type>
// <scope>import</scope>
// </dependency>
// </dependencies>
// </dependencyManagement>
//
// <dependencies>
// <dependency>
// <groupId>io.vertx</groupId>
// <artifactId>vertx-core</artifactId>
// </dependency>
// <dependency>
// <groupId>io.vertx</groupId>
// <artifactId>vertx-web-client</artifactId>
// </dependency>
// <dependency>
// <groupId>io.vertx</groupId>
// <artifactId>vertx-rx-java</artifactId>
// </dependency>
//
// <dependency>
// <groupId>org.slf4j</groupId>
// <artifactId>slf4j-jdk14</artifactId>
// <version>LATEST</version>
// </dependency>
//
// <dependency>
// <groupId>com.jayway.jsonpath</groupId>
// <artifactId>json-path</artifactId>
// <version>LATEST</version>
// <exclusions>
// <exclusion>
// <groupId>org.slf4j</groupId>
// <artifactId>slf4j-api</artifactId>
// </exclusion>
// </exclusions>
// </dependency>
//
// </dependencies>
/**
* Simple sample application for connecting to the sandbox api of ING Bank.
* Only dependencies are vertx and jsonpath.
*
* See https://developer.ing.com/openbanking/get-started
*
* Convert TLS key and certificate to p12 format as follows:
* openssl pkcs12 -export -out keystore_tls.p12 -inkey example_client_tls.key -in example_client_tls.cer
*/
public class INGBasicExample {
private static final Logger LOG = Logger.getLogger(INGBasicExample.class.getName());
private static final DateTimeFormatter DATEFORMATTER = DateTimeFormatter.ofPattern("E, dd MMM yyyy HH:mm:ss O", Locale.US);
private static final String ING_API_BASE_URL = "https://api.sandbox.ing.com";
private static final String TRANSPORT_KEYSTORE_FILE = "src/main/resources/keystore_tls.p12";
private static final String TRANSPORT_KEYSTORE_PW = "changeit";
private static final String SIGNING_KEY_FILE = "src/main/resources/example_client_signing.key";
private static final PrivateKey PRIVATE_KEY_FOR_SIGNING = initPrivateKeyForSigning();
private static PrivateKey initPrivateKeyForSigning() {
Path path = Paths.get(SIGNING_KEY_FILE);
LOG.log(Level.INFO,"Reading file {0}", path.toAbsolutePath());
try {
List<String> lines = Files.readAllLines(path);
String pem = lines.stream().limit(lines.size()-1).skip(1).collect(Collectors.joining());
byte[] encoded = Base64.getDecoder().decode(pem);
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(encoded);
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePrivate(spec);
} catch (IOException | GeneralSecurityException e) {
throw new RuntimeException("Unable to read key "+SIGNING_KEY_FILE, e);
}
}
private static String createSignature(String stringToSign) {
try {
byte[] data = stringToSign.getBytes("UTF8");
Signature sig = Signature.getInstance("SHA256withRSA");
sig.initSign(PRIVATE_KEY_FOR_SIGNING);
sig.update(data);
byte[] signatureBytes = sig.sign();
String base64Signature = Base64.getEncoder().encodeToString(signatureBytes);
return base64Signature;
} catch (UnsupportedEncodingException | GeneralSecurityException e) {
throw new RuntimeException("Problem creating signature: "+e, e);
}
}
private static String createDigest(String text) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(text.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(hash);
} catch (GeneralSecurityException e) {
throw new RuntimeException("Problem creating digest: "+e, e);
}
}
public static void main(String[] args) {
Vertx vertx = Vertx.vertx();
String clientId = "e77d776b-90af-4684-bebc-521e5b2614dd"; // fixed; given by ING, see docs
HttpMethod httpMethod = HttpMethod.POST;
String pathWithoutQuery = "/oauth2/token";
String pathWithQuery = pathWithoutQuery+"";
String body = "grant_type=client_credentials";
String date = ZonedDateTime.now(ZoneOffset.UTC).format(DATEFORMATTER);
String reqId = UUID.randomUUID().toString();
String digest = "SHA-256="+createDigest(body);
String toSign = String.format("(request-target): %s %s\ndate: %s\ndigest: %s\nx-ing-reqid: %s",
httpMethod.name().toLowerCase(), pathWithoutQuery, date, digest, reqId);
String signature = createSignature(toSign);
LOG.log(Level.INFO,"toSign=\n{0}", toSign);
LOG.log(Level.INFO,"* signature=\n* {0}", new Object[]{signature});
// Setup mutual ssl webclient
WebClient webClient = WebClient.create(vertx,
new WebClientOptions()
.setSsl(true)
.setKeyStoreOptions(new JksOptions()
.setPath(TRANSPORT_KEYSTORE_FILE)
.setPassword(TRANSPORT_KEYSTORE_PW)));
Single<HttpResponse<String>> httpResponseSingle = webClient
.requestAbs(HttpMethod.POST, ING_API_BASE_URL+pathWithQuery)
.putHeader("Accept", "application/json")
.putHeader("Content-Type", "application/x-www-form-urlencoded")
.putHeader("Content-Length", ""+body.length())
.putHeader("Digest", digest)
.putHeader("Date", date)
.putHeader("X-ING-ReqID", reqId)
.putHeader("Authorization", "Signature keyId=\""+clientId+"\",algorithm=\"rsa-sha256\",headers=\"(request-target) date digest x-ing-reqid\",signature=\"" + signature + "\"")
.as(BodyCodec.string())
.rxSendBuffer(Buffer.buffer(body));
httpResponseSingle
.map(resp -> {
LOG.log(Level.INFO, "Response = {0}\n{1}", new Object[]{resp.statusCode(), resp.body()});
return JsonPath.read(resp.body(), "$.access_token");
})
.subscribe(accessToken -> {
LOG.log(Level.INFO, "*** GOT ACCESS TOKEN: {0}", accessToken);
vertx.close();
}, t -> {
LOG.log(Level.SEVERE, "Error: {0}", t.toString());
vertx.close();
});
}
}
@fschutte
Copy link
Author

I have updated the gist to make it work in the newest version of the ING Sandbox API.

@txamito
Copy link

txamito commented Sep 29, 2020

YOU are the best! Thanks for this...

@laurentlemaire
Copy link

Can you access payment information (payment history...) in production easily or does it requires some kind of authority/admin approval?

@fschutte
Copy link
Author

fschutte commented Oct 4, 2020

Can you access payment information (payment history...) in production easily or does it requires some kind of authority/admin approval?

Yeah, for production you'll need to be an accredited PSD2 party (TPP) with an eIDAS certificate and such..

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment