Filed under: Java, — Tags: Jersey, https, rest — Thomas Sundberg — 2019-01-27
When testing a rest service behind https
, you must be able to create a client that supports an encrypted connection.
The trick is to create a javax.ws.rs.client.Client
and set the sslContext
as well as
the hostnameVerifier()
properly.
The factory below creates a Jersey client.
package se.thinkcode;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
class JerseyHttpClientFactory {
static Client getJerseyHTTPSClient() throws KeyManagementException, NoSuchAlgorithmException {
SSLContext sslContext = getSslContext();
HostnameVerifier allHostsValid = new NoOpHostnameVerifier();
return ClientBuilder.newBuilder()
.sslContext(sslContext)
.hostnameVerifier(allHostsValid)
.build();
}
private static SSLContext getSslContext() throws NoSuchAlgorithmException,
KeyManagementException {
SSLContext sslContext = SSLContext.getInstance("TLSv1");
KeyManager[] keyManagers = null;
TrustManager[] trustManager = {new NoOpTrustManager()};
SecureRandom secureRandom = new SecureRandom();
sslContext.init(keyManagers, trustManager, secureRandom);
return sslContext;
}
}
TrustManager
that thinks that the world is trustworthyThe first you need is a TrustManager
that trusts everything. My implementation looks like this:
package se.thinkcode;
import javax.net.ssl.X509TrustManager;
import java.security.cert.X509Certificate;
public class NoOpTrustManager implements X509TrustManager {
@Override
public void checkClientTrusted(X509Certificate[] x509Certificates, String s) {
}
@Override
public void checkServerTrusted(X509Certificate[] x509Certificates, String s) {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}
It doesn't do anything and will therefore not stop anything. It is rubbish for anything but testing. If you are looking for a secure solution, look somewhere else.
HostnameVerifier
that thinks the best of all hostsThe next thing you need is a HostnameVerifier
that trust the world. An implantation may look like this:
package se.thinkcode;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;
public class NoOpHostnameVerifier implements HostnameVerifier {
@Override
public boolean verify(String s, SSLSession sslSession) {
return true;
}
}
As you can see, it trusts anything as it always return true in verify(()
.
These three pieces will allow you to create a http
client that supports https
for testing.
This client should be used for testing. I used it in a test that looks like this:
package se.thinkcode;
import org.junit.Test;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.MediaType;
import static org.assertj.core.api.Assertions.assertThat;
import static se.thinkcode.JerseyHttpClientFactory.getJerseyHTTPSClient;
public class CurrencyTradeTest {
@Test
public void currency_trade_jersey() throws Exception {
String actual = post();
assertThat(actual).doesNotContain("requested resource could not be found");
assertThat(actual.length()).isEqualTo(24);
}
private String post() throws Exception {
String url = "https://currencytrade-spray.herokuapp.com";
String payload = TradeOrder.getOrder();
Entity<String> tradeOrder = Entity.entity(payload, MediaType.APPLICATION_JSON);
Client client = getJerseyHTTPSClient();
try {
return client
.target(url)
.path("v1")
.path("trade")
.request()
.post(tradeOrder)
.readEntity(String.class);
} finally {
client.close();
}
}
}
The last important component is the dependencies I used for this example. They are declared in the build script:
plugins {
id 'java'
}
compileJava {
sourceCompatibility = 1.8
targetCompatibility = 1.8
}
dependencies {
compile 'org.glassfish.jersey.core:jersey-client:2.28'
compile 'org.glassfish.jersey.inject:jersey-hk2:2.28'
compile 'org.json:json:20180813'
testCompile 'junit:junit:4.12'
testCompile 'org.assertj:assertj-core:3.11.1'
}
repositories {
mavenCentral()
}
That it folks, a Jersey client that supports https
.
I would like to thank Malin Ekholm and Mika Kytöläinen for feedback.