5.4. Web サービスクライアントのセキュリティー保護

概要

基本的な Camel CXF プロキシーのデモンストレーションでは、Web サービスクライアントは実際には src/test ディレクトリーに JUnit テストとして実装されます。そのため、クライアントは Maven コマンド mvn test を使用して簡単に実行できます。クライアントで SSL/TLS セキュリティーを有効にするため、テストクライアントの Java 実装が完全に置き換えられ、SSL/TLS 設定を含む Spring ファイルが src/test/resources/META-INF/spring ディレクトリーに追加されます。クライアントを設定するために実行する必要のある手順を説明する前に、このセクションでクライアントの Java コードと Spring 設定の詳細について説明します。

暗黙的な設定

エンドポイントアドレスの URL スキームを https: に変更する以外に、クライアントプロキシーで SSL/TLS セキュリティーを有効にする設定のほとんどは、Spring 設定の http:conduit 要素に含まれます。ただし、この設定をクライアントプロキシーに適用する方法は、混乱を招く可能性があります。それは、http:conduit 要素によってクライアントプロキシーが明示的に参照されず、クライアントプロキシーは http:conduit 要素を明示的に参照しないためです。http:conduit 要素とクライアントプロキシー間の接続は暗黙的に確立され、http:conduit で暗黙的に設定されたクライアントプロキシー が示すように、いずれも同じ WSDL ポートを参照します。

http:conduit で暗黙的に設定されたクライアントプロキシー

Element

camel cxf 02

以下のように、クライアントプロキシーと http:conduit 要素間の接続が確立されます。

  1. クライアントは http:conduit 要素が含まれる Spring 設定ファイルをロードおよび解析します。
  2. http:conduit Bean が作成されると、対応するエントリーがレジストリーに作成されます。これは、指定の WSDL ポート名の下に Bean への参照を保存します (名前は QName 形式で保存されます)。
  3. JAX-WS クライアントプロキシーが作成されると、レジストリーをスキャンし、プロキシーの WSDL ポート名に関連付けられた http:conduit Bean を見つけられるかどうかを確認します。そのような Bean が見つかると、設定の詳細がプロキシーに自動的に挿入されます。

クライアント側で必要な証明書

クライアントは、src/main/resources/certs ディレクトリーからの以下の clientKeystore.jks キーストアファイルで設定されます。このキーストアには、次の 2 つのエントリーが含まれています。

信頼できる証明書エントリー
サーバー証明書とクライアント証明書の両方を発行して署名した CA 証明書を含む信頼できる証明書エントリー。
秘密鍵の入力
クライアント自身の X.509 証明書と秘密鍵を含む秘密鍵エントリー。実際、サーバーでは TLS ハンドシェイク中にクライアントによる証明書の送信が必要ないため、現在の例を実行するためにこの証明書が必ず必要なわけではありません (例5.2「SSL/TLS が有効になっている httpj:engine-factory 要素」 を参照)。

Spring 定義をクライアントにロード

サンプルクライアントは Spring コンテナーに直接デプロイされていませんが、セキュアな HTTP コンジットを定義するためにいくつかの Spring 定義が必要です。では、Spring コンテナーなしで Spring 定義を作成するにはどうすればよいでしょうか。org.apache.cxf.bus.spring.SpringBusFactory クラスを使用すると、Spring 定義を Java ベースのクライアントに簡単に読み取ることができます。

以下のコードは、META-INF/spring/cxf-client.xml ファイルから Spring 定義を読み取り、これらの定義を取り入れた Apache CXF Bus オブジェクトを作成する方法を示しています。

// Java
import org.apache.cxf.bus.spring.SpringBusFactory;
...
protected void startCxfBus() throws Exception {
    bf = new SpringBusFactory();
    Bus bus = bf.createBus("META-INF/spring/cxf-client.xml");
    bf.setDefaultBus(bus);
}

クライアントプロキシーの作成

原則として、WSDL プロキシーの作成にはいくつかの方法があります。JAX-WS API を使用して WSDL ファイルの内容に基づいてプロキシーを作成したり、WS-WS API を使用して WSDL ファイルなしでプロキシーを作成できます。また、Apache CXF 固有のクラス JaxWsProxyFactoryBean を使用してプロキシーを作成できます。

この SSL/TLS クライアントの場合、次の Java サンプルに示すように、最も便利なアプローチは、JAX-WS API を使用して WSDL ファイルを使用せずにプロキシーを作成することです。

// Java
import javax.xml.ws.Service;
import org.apache.camel.example.reportincident.ReportIncidentEndpoint;
...
// create the webservice client and send the request
Service s = Service.create(SERVICE_NAME);
s.addPort(
    PORT_NAME,
    "http://schemas.xmlsoap.org/soap/",
    ADDRESS_URL
  );
ReportIncidentEndpoint client =
  s.getPort(PORT_NAME, ReportIncidentEndpoint.class);
注記

この例では、JaxWsProxyFactoryBean のアプローチを使用してプロキシーを作成できません。これは、このように作成されたプロキシーは Spring 設定ファイルで指定された HTTP コンジット設定を見つけることができないためです。

SERVICE_NAME および PORT_NAME 定数は、例5.1「ReportIncidentEndpointService WSDL サービス」 で定義されているように、それぞれ WSDL サービスおよび WSDL ポートの QNames です。ADDRESS_URL 文字列には、プロキシー Web サービスアドレスと同じ値があり、以下のように定義されます。

private static final String ADDRESS_URL =
  "https://localhost:9080/camel-example-cxf-proxy/webservices/incident";

特に、このアドレスは URL スキーム https で定義し、SSL/TLS 経由の HTTP を選択する必要があることに注意してください

クライアントへの SSL/TLS セキュリティー追加手順

SSL/TLS セキュリティーが有効になっている JAX-WS クライアントを定義するには、次の手順を実行します。

テストケースとして Java クライアントを作成

例5.3「ReportIncidentRoutesTest Java クライアント」 は、JUnit テストケースとして実装された Java クライアントの完全なコードを示しています。このクライアントは、examples/camel-example-cxf-proxy デモンストレーションの src/test/java/org/apache/camel/example/reportincident サブディレクトリーに、既存のテストである ReportIncidentRoutesTest.java を置き換えます。

クライアントを CamelInstallDir/examples/camel-example-cxf-proxy デモに追加するには、src/test/java/org/apache/camel/example/reportincident サブディレクトリーに移動し、既存の ReportIncidentRoutesTest.java ファイルをバックアップの場所に移動し、新しい ReportIncidentRoutesTest.java ファイルを作成して、例5.3「ReportIncidentRoutesTest Java クライアント」 のコードをこのファイルに貼り付けます。

例5.3 ReportIncidentRoutesTest Java クライアント

// Java
package org.apache.camel.example.reportincident;

import org.apache.camel.spring.Main;
import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;
import org.junit.Test;

import java.net.URL;
import javax.xml.namespace.QName;
import javax.xml.ws.Service;

import org.apache.cxf.Bus;
import org.apache.cxf.bus.spring.SpringBusFactory;
import org.apache.camel.example.reportincident.ReportIncidentEndpoint;
import org.apache.camel.example.reportincident.ReportIncidentEndpointService;

import static org.junit.Assert.assertEquals;

/**
 * Unit test of our routes
 */
public class ReportIncidentRoutesTest {

    private static final QName SERVICE_NAME
        = new QName("http://reportincident.example.camel.apache.org", "ReportIncidentEndpointService");

    private static final QName PORT_NAME =
        new QName("http://reportincident.example.camel.apache.org", "ReportIncidentEndpoint");

    private static final String WSDL_URL = "file:src/main/resources/etc/report_incident.wsdl";

    // should be the same address as we have in our route
    private static final String ADDRESS_URL = "https://localhost:9080/camel-example-cxf-proxy/webservices/incident";

    protected SpringBusFactory bf;

    protected void startCxfBus() throws Exception {
        bf = new SpringBusFactory();
        Bus bus = bf.createBus("META-INF/spring/cxf-client.xml");
        bf.setDefaultBus(bus);
    }

    @Test
    public void testRendportIncident() throws Exception {
        startCxfBus();
        runTest();
    }

    protected void runTest() throws Exception {

        // create input parameter
        InputReportIncident input = new InputReportIncident();
        input.setIncidentId("123");
        input.setIncidentDate("2008-08-18");
        input.setGivenName("Claus");
        input.setFamilyName("Ibsen");
        input.setSummary("Bla");
        input.setDetails("Bla bla");
        input.setEmail("davsclaus@apache.org");
        input.setPhone("0045 2962 7576");

        // create the webservice client and send the request
        Service s = Service.create(SERVICE_NAME);
        s.addPort(PORT_NAME, "http://schemas.xmlsoap.org/soap/", ADDRESS_URL);
        ReportIncidentEndpoint client = s.getPort(PORT_NAME, ReportIncidentEndpoint.class);

        OutputReportIncident out = client.reportIncident(input);

        // assert we got a OK back
        assertEquals("OK;456", out.getCode());
    }
}

Spring 設定への http:conduit 要素の追加

例5.4「SSL/TLS が有効になっている http:conduit 要素」 は、ReportIncidentEndpoint WSDL ポートの http:conduit 要素を定義する Spring 設定を示しています。http:conduit 要素は、指定の WSDL ポートを使用するクライアントプロキシーの SSL/TLS セキュリティーを有効にするように設定されます。

クライアントテストケースに Spring 設定を追加するには、src/test/resources/META-INF/spring サブディレクトリーを作成し、お気に入りのテキストエディターを使用してファイル cxf-client.xml を作成し、例5.4「SSL/TLS が有効になっている http:conduit 要素」 の内容をそのファイルに貼り付けます。

例5.4 SSL/TLS が有効になっている http:conduit 要素

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:cxf="http://camel.apache.org/schema/cxf"
       xmlns:sec="http://cxf.apache.org/configuration/security"
       xmlns:http="http://cxf.apache.org/transports/http/configuration"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://camel.apache.org/schema/cxf http://camel.apache.org/schema/cxf/camel-cxf.xsd
       http://cxf.apache.org/configuration/security http://cxf.apache.org/schemas/configuration/security.xsd
       http://cxf.apache.org/transports/http/configuration http://cxf.apache.org/schemas/configuration/http-conf.xsd
       ">

  <http:conduit name="{http://reportincident.example.camel.apache.org}ReportIncidentEndpoint.http-conduit">
    <http:tlsClientParameters disableCNCheck="true" secureSocketProtocol="TLSv1">
      <sec:keyManagers keyPassword="ckpass">
          <sec:keyStore password="cspass" type="JKS"
          resource="certs/clientKeystore.jks" />
      </sec:keyManagers>
      <sec:trustManagers>
          <sec:keyStore password="cspass" type="JKS"
          resource="certs/clientKeystore.jks" />
      </sec:trustManagers>
      <sec:cipherSuitesFilter>
        <sec:include>.*_WITH_3DES_.*</sec:include>
        <sec:include>.*_WITH_DES_.*</sec:include>
        <sec:exclude>.*WITH_NULL.</sec:exclude>*
        <sec:exclude>.*DH_anon.</sec:exclude>*
      </sec:cipherSuitesFilter>
    </http:tlsClientParameters>
   </http:conduit>

</beans>

上記の設定については、次の点に注意してください。

  • http:conduit 要素を定義するのに、http: および sec: 名前空間の接頭辞が必要です。xsi:schemaLocation 要素では、対応する http://cxf.apache.org/configuration/security および http://cxf.apache.org/transports/http/configuration 名前空間の場所を指定する必要もあります。
  • http:tlsClientParameters 要素の disableCNCheck 属性は true に設定されます。これは、サーバーの X.509 証明書のコモンネームがサーバーのホスト名と一致するかどうかをチェック しない ことを意味します。詳細は 付録A 証明書の管理 を参照してください。

    重要

    実稼働環境では、CN チェックを無効にすることはお勧め しません

  • sec:keystore 要素では、クラスパス上の証明書を見つける resource 属性を使用して証明書の場所が指定されます。Maven がテストを実行すると、証明書が src/main/resources/certs ディレクトリーから読み取りできるように、クラスパスで src/main/resources の内容を自動的に利用できるようにします。

    注記

    また、ファイルシステム内を検索する file 属性を使用して、証明書の場所を指定するオプションもあります。ただし、resource 属性は、バンドルにパッケージ化されたアプリケーションでの使用に適しています。

  • sec:cipherSuitesFilter 要素は、.*WITH_NULL.\* および .*DH_anon.\* に一致する暗号スイートを除外するように設定されています。これらの暗号化スイートは事実上不完全であり、通常の使用を目的としたものでは ありません

    重要

    .*WITH_NULL.\* および .*DH_anon.\* に一致する暗号を常に除外することが推奨されます。

  • secureSocketProtocol 属性は、サーバーのプロトコルと一致するように TLSv1 に設定し、SSLv3 プロトコルが使用されないようにする必要があります (POODLE セキュリティー脆弱性 (CVE-2014-3566))。

クライアントの実行

クライアントはテストケースとして定義されているため、標準の Maven テスト目標を使用してクライアントを実行できます。クライアントを実行するには、新しいコマンドウィンドウを開き、CamelInstallDir/examples/camel-example-cxf-proxy ディレクトリーに移動し、以下の Maven コマンドを入力します。

mvn test

テストが正常に実行されると、OSGi コンソールウィンドウに次の出力が表示されます。

Incident was 123, changed to 456

Invoked real web service: id=456 by Claus Ibsen