LoginSignup
4
6

More than 5 years have passed since last update.

JavaでSSLでRESTを投げるときに、プロキシを通す方法

Last updated at Posted at 2017-01-03

Java で RESTをなげるサービスの開発をしていて、自分がなげているリクエストはあってるかな?って確認したいケースがあります。
そんなときはプロキシを通すわけですが、そのやりかたを調べたことがあって、その結果を整理しておきます。
JavaのREST Clientとしては JAX-RSのリファレンス実装にもなっている Jerseyのクライアント部分を用いています。

先人の知恵にはいつも感謝ですね。基本的にはこのサイトの情報を参考にさせていただきました。
JerseyClientでプロキシを通す方法

なのですが、すくなくとも今回アクセスしているAPIではSSLの下記のエラー

javax.ws.rs.ProcessingException: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException:
 PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: 
 unable to find valid certification path to requested target

が発生してしまうようで、追加で調査したところを中心にまとめます。

pom.xml の設定

pom.xml
<!-- だたJersey のクライアントをつかうだけなら -->
<dependency>
  <groupId>org.glassfish.jersey.core</groupId>
  <artifactId>jersey-client</artifactId>
  <version>2.25</version>
</dependency>

<!-- さらにJerseyでProxyを使う場合。 -->
<dependency>
  <groupId>org.glassfish.jersey.connectors</groupId>
  <artifactId>jersey-apache-connector</artifactId>
  <version>2.25</version>
</dependency>

クライアントの構築

アクセス先はQiitaのAPI

https://qiita.com/api/v2/users/qiita/items?per_page=1&page=4

を対象にしてみます。

まずはcurlで。

curl 'https://qiita.com/api/v2/users/qiita/items' -d 'per_page=1' -d 'page=4' -G

なにかJSONが返ってきたと思います。

Javaで

つぎにJavaです。まずはプロキシを通さないパターンです。

下記のドキュメントを参考に実装していきます。
Jersey 2.25 User Guide/5. Client API

ClientSamples.java

import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

public class ClientSamples {

    public static void main(String[] args) {
        new ClientSamples().execute();
    }

    public void execute() {
        String target = "https://qiita.com";
        String path = "/api/v2/users/qiita/items";

        // Client client = createSecureClient();
        Client client = createClient();
        Response restResponse = client
                .target(target)
                .path(path)
                .queryParam("page", "4")
                .queryParam("per_page", "1")
                .request(MediaType.APPLICATION_JSON_TYPE)
                .get();

        System.out.println(restResponse.readEntity(String.class));
    }

    private Client createClient() {
        return ClientBuilder.newClient();
    }

}

curlの場合と、おなじ結果が得られるはずです。

プロキシを通してみる。

下記のページならびに先ほど参考にさせていただきましたと書いたサイトを参考に。
JerseyClientでプロキシを通す方法
Jersey 2.25 User Guide/5.9. Securing a Client

ClientSamples.java
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import org.glassfish.jersey.apache.connector.ApacheConnectorProvider;
import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.client.ClientProperties;

public class ClientSamples {

    public static void main(String[] args) {
        new ClientSamples().execute();
    }

    public void execute() {
        String target = "https://qiita.com";
        String path = "/api/v2/users/qiita/items";

        Client client = createSecureClient();
        // Client client = createClient();
        Response restResponse = client
                .target(target)
                .path(path)
                .queryParam("page", "4")
                .queryParam("per_page", "1")
                .request(MediaType.APPLICATION_JSON_TYPE).get();

        System.out.println(restResponse.readEntity(String.class));
    }

    private Client createSecureClient() {
        String proxyHost = "http://127.0.0.1:8080";
        ClientConfig config = new ClientConfig();

        // providerをproxy対応?にする
        config.connectorProvider(new ApacheConnectorProvider());
        config.property(ClientProperties.PROXY_URI, proxyHost);
        // config.property(ClientProperties.PROXY_USERNAME, "userName");
        // config.property(ClientProperties.PROXY_PASSWORD, "password");

        // builderの生成
        ClientBuilder b = ClientBuilder.newBuilder().withConfig(config);
        return b.build();
    }

}

実行したところやはり、、javax.net.ssl.SSLHandshakeException が発生します。プロキシを通したことで、信頼できないSSLサーバに接続しようとしているように見えているっぽいですね。なので、SSL証明書の検証処理の無効化、ホスト名検証処理の無効化をする下記のコードを挿入します。

ClientSamples.java
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.X509TrustManager;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import org.glassfish.jersey.apache.connector.ApacheConnectorProvider;
import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.client.ClientProperties;


public class ClientSamples {

    public static void main(String[] args) {
        new ClientSamples().execute();
    }

    public void execute() {
        String target = "https://qiita.com";
        String path = "/api/v2/users/qiita/items";

        Client client = createSecureClient();
        // Client client = createClient();
        Response restResponse = client
                .target(target)
                .path(path)
                .queryParam("page", "4")
                .queryParam("per_page", "1")
                .request(MediaType.APPLICATION_JSON_TYPE)
                .get();

        System.out.println(restResponse.readEntity(String.class));
    }

    private Client createClient() {
        return ClientBuilder.newClient();
    }

    private Client createSecureClient() {
        String proxyHost = "http://127.0.0.1:8080";
        ClientConfig config = new ClientConfig();

        // providerをproxy対応?にする
        config.connectorProvider(new ApacheConnectorProvider());
        config.property(ClientProperties.PROXY_URI, proxyHost);
        // config.property(ClientProperties.PROXY_USERNAME, "userName");
        // config.property(ClientProperties.PROXY_PASSWORD, "password");

        SSLContext sslContext = createSSLContext();
        HostnameVerifier hostnameVerifier = createHostNameVerifier();

        // builderの生成
        ClientBuilder b = ClientBuilder.newBuilder().withConfig(config)
                .sslContext(sslContext).hostnameVerifier(hostnameVerifier);
        return b.build();
    }

    private SSLContext createSSLContext() {
        SSLContext sslContext = null;
        try {
            sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null,
                    new X509TrustManager[] { new X509TrustManager() {
                        @Override
                        public void checkClientTrusted(X509Certificate[] chain,String authType) throws CertificateException {}
                        @Override
                        public void checkServerTrusted(X509Certificate[] chain,String authType) throws CertificateException {}
                        @Override
                        public X509Certificate[] getAcceptedIssuers() {return new X509Certificate[0];}
                    } }, new SecureRandom());
            // HttpsURLConnection
            // .setDefaultSSLSocketFactory(sslContext.getSocketFactory());

        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (KeyManagementException e) {
            e.printStackTrace();
        }
        return sslContext;
    }

    private HostnameVerifier createHostNameVerifier() {
        return new HostnameVerifier() {
            @Override
            public boolean verify(String hostname, SSLSession session) {return true;}
        };
    }
}

これでうまくいくようです。。テストサーバにオレオレ証明書を入れているケースなどにも対応できそうですね。

関連リンク

JerseyClientでプロキシを通す方法
Jersey 2.25 User Guide/5. Client API
Jersey 2.25 User Guide/5.9. Securing a Client

4
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
6