A Jersey client supporting https

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.

Jersey client factory

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;
    }
}

A TrustManager that thinks that the world is trustworthy

The 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.

A HostnameVerifier that thinks the best of all hosts

The 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.

A test class

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();
        }
    }
}

Dependencies

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()
}

Conclusion

That it folks, a Jersey client that supports https.

Acknowledgements

I would like to thank Malin Ekholm and Mika Kytöläinen for feedback.

Resources



(less...)

Pages

About
Events
Why

Categories

Agile
Automation
BDD
Clean code
Continuous delivery
Continuous deployment
Continuous integration
Cucumber
Culture
Design
DevOps
Executable specification
Gradle
Guice
J2EE
JUnit
Java
Kubernetes
Linux
Load testing
Maven
Mockito
New developers
Pair programming
PicoContainer
Presentation
Programming
Public speaking
Quality
Recruiting
Requirements
Scala
Selenium
Software craftsmanship
Software development
Spring
TDD
Teaching
Technical debt
Test automation
Tools
Web
Windows
eXtreme Programming

Authors

Thomas Sundberg
Adrian Bolboaca

Archives

Meta

rss RSS