Topics
Enterprise Architecture
Oracle WebLogic Server 9.2によるWebサービスの保護
ページ:
1,
2,
3
次に、単純なJavaクライアントを構築し、作成したサービスのテストをおこないます。サービスのテストには、 WebLogic Serverのコンソール・ツールを使用することもできますが、セキュリティ関連のテストをおこなうために、ゆくゆくはクライアントが必要となります。
クライアントでサービスを正常に呼び出せるようにするには、まず、そのサービスのクライアント・プロキシを生成する必要があります。この処理は、別のAntスクリプトを使用して実行します。
WORKSPACE_DIR/WSTestフォルダで、
gen-client.xmlという名前のテキスト・ファイルを新しく作成し、その内容を以下のように設定します。
<project default="build-client">
<taskdef name="clientgen"
classname="weblogic.wsee.tools.anttasks.ClientGenTask"
<target name="build-client">
<clientgen
wsdl="HelloWorldService.wsdl"
destDir="."
packageName="com.test.client"/>
</target>
</project>
以前に開いたコマンド・シェルから、コマンド
ant -buildfile gen-client.xmlを実行します。
BUILD SUCCESSFULメッセージが表示されます。これにより、クライアント・プロキシが生成されます。
WTPに戻り、WSTestプロジェクトをリフレッシュします。新しく生成した
com.test.clientパッケージが表示されます。また、WTPにより、新規に生成されたプロキシのエラーが報告されます。このエラーを修正するには、プロジェクトに
webserviceclient.jarライブラリを追加します。これは、プロジェクトに
weblogic.jarを追加したときと同じ方法で実行できます(
webserviceclient.jarは、
weblogic.jarと同じフォルダにあります)。これで、クライアントのコーディングを開始できます。
WTPで、WSTestプロジェクト内に
com.test.client.HelloWorldClientという名前の新規Javaクラスを作成します。このクラスのソースを以下のように設定します。
package com.test;
import com.test.client.*;
public class HelloWorldClient {
public static void main(String[] args) throws Throwable {
com.test.HelloWorldService service = new HelloWorldService_Impl();
HelloWorldPortType port = service.getHelloWorldPortTypeSoapPort();
String greeting = port.sayHello("Gary");
System.out.println("The greeting returned was: " + greeting);
}
}
このクライアントは、クライアント・プロキシ・クラスを利用して対象のサービスに対する参照を取得してから、そのサービスの
sayHelloメソッドを呼び出すという、非常にシンプルなものです。
コードを実行すると、それに応じたコンソール出力が表示されます。
The greeting returned was: Hello there, Gary
ただし、TCP/IPモニターが有効化されていても、このテストでは使用できないため注意してください。これ は、クライアントが、監視対象のポート7002ではなく、ポート7001でサーバーと直接やりとりをするためです。これを変更する方法については、後述の 項で説明します。
これで、非常にシンプルなWebサービスとクライアントを構築することができましたが、セキュリティ・メカニズムについてはまだ指定されていません。次の項では、この点について取り上げていきます。
メッセージの整合性(と暗号化)をなんらかの形でおこなうには、一連の鍵が必要となります。鍵は 証明書の基盤となるもので、この証明書を使用して各メッセージに一意の 署名をおこなうことで、メッセージの整合性が保証されます。送信者は、メッセージを送る際に自分の証明書を添付でき、一方受信者側では、受け取った証明書をチェックすることで、送信者のIDを検証し、メッセージが送信中に改ざんされていないかどうかを確認できます。
また、メッセージに整合性を追加するためには、クライアント側で証明書とあわせてデジタル署名を送信する必要があります。この場合、証明書だけでは意味は ありません。クライアントは、メッセージのハッシュを取得し、秘密鍵を使用してそのハッシュを暗号化することで、デジタル署名を生成します。サーバーで は、メッセージに添付されたクライアントの証明書(公開鍵)を使用してハッシュを復号化し、そのクライアントから送られてきたものであることを確認しま す。次に、復号化したハッシュを、メッセージから取得したハッシュと比較します。2つのハッシュが一致すれば、メッセージが改ざんされていないことが証明 されます。このチュートリアルでは、WebLogic Serverとクライアントで通信をおこないますが、どちらのサイドにも鍵が必要となります。
WebLogic Serverには、デモンストレーション用の"ダミー"の鍵が用意されています。これは、このチュートリアルのデモンストレーションで使用するには十分で すが、本番用としては使用できません。ここでは、このダミーの鍵を使用しますが、本番環境では新しく鍵を生成する必要があることに注意してください。クラ イアントとサーバーの両方に対する鍵を作成する場合は、すぐに使用できるように用意された
keytoolコマンドにより実行が可能です。
ここで、クライアントの鍵を作成します。具体的には、クライアント用の鍵のペア(公開鍵と、それに関連づけられた秘密鍵)を作成することになります。この 鍵のペアのうち、秘密鍵がメッセージの復号化とデジタル署名に使用されます。また、鍵のペアはキーストア・ファイル内に保存されます。キーストアは、鍵の ペアを作成する際に自動的に作成されます。指定したキーストアがすでにある場合は、新しい鍵のペアがそこに追加されます。公開鍵は、X.509 証明書内に含まれています。証明書と秘密鍵はキーストア内に保存され、別名で識別されます。
新しいコマンド・シェルを開き、
cdを実行してBEA JRE binディレクトリに移動します(通常、このディレクトリは
C:\bea\jdk150_04\jre\binのようになっています)。ここで、以下のコマンドを入力します。
keytool -genkey -keyalg RSA -keystore C:\client_keystore.jks -storepass abc123 -alias client_key -keypass client_key_password -dname "CN=Client, OU=WEB AGE, C=US" -keysize 1024 -validity 1460
(このコマンドにより、ルート・ディレクトリにキーストアが作成されます。キーストアの場所は、使用している環境に合わせてより使いやすい場所に変更できます。 Unixベースのオペレーティング・システムを使用している場合は、ファイルの場所を、"C:\"ではなくUNIXファイル・システムの標準ネーミング規則に基づいた名前に変更してください。)
ここでは、作成したキーストアの名前を
C:\client_keystore.jksとし、キーストアにアクセスするためのパスワードとして
abc123を使用します。鍵のペアを参照するために使用する別名は
client_key、秘密鍵を保護するためのパスワードは
client_key_passwordです。鍵のペアを作成するためのアルゴリズムには、RSAを使用します。
client_key aliasに関連づけられた識別名は
CN=Client, OU=WEB AGE, C=USで、これは証明書内の発行者フィールドとサブジェクト・フィールドとして使用されます。鍵の長さは1024ビット(BEAで求められる最小値)です。最後に、対応する証明書の有効期間を1460日(4年間)に設定します。 設定が正常に完了すると、C:\.に、client_keystore.jksという名前の新しいファイルが表示されます。これが、クライアントで使用されるキーストアとなります。
ここで、鍵が適切に作成されたかどうかを検証する必要があります。コマンド・プロンプト・ウィンドウで、次のように入力します。
keytool -list -keystore C:\client_keystore.jks -storepass abc123 -v | findstr Alias
このコマンドにより、次のような出力が表示されます。
Alias name: client_key
これで、キーストアが正常に作成されました。クライアントのキーストアには、公開鍵(証明書)と、
client_keyという名前のエンティティに対する秘密鍵の両方が含まれます。この2つの鍵は、デジタル署名の送信と暗号化で必要となります。
この2番目のコマンド・シェル・ウィンドウは、またあとで必要となるため、ここでは終了しないでください。
次に、クライアントの証明書が信頼できるものであることをサーバーに対して立証する必要があります。
WebLogic Serverでは、トラスト・ファイルが管理されています。このファイルは、基本的には証明書をまとめたリストのようなもので、このリストに含まれていれ ば、WebLogic Serverで信頼性があると判断されて受け入れられることになります。ただし、前述の手順で生成したクライアントの鍵は、トラスト・ファイルには含まれ ません。つまり、WebLogic Serverが証明書を受信したとしても、その証明書は即座に拒否されることとなります。そのため、クライアントの証明書のサーバーのトラストストアへの インポートが必要となります。
この処理には、2つのプロセスがあります。つまり、まずクライアントのキーストアから証明書をエクスポートし、そのあと、その証明書をサーバーのトラスト・ファイルにインポートする必要があります。現時点では、サーバーは"デモ"トラスト・ファイル(
Demotrust.jks)を使用するように設定されており、このトラスト・ファイルは次の場所にある標準のJDKトラスト・ファイルを参照しています。
C:\bea\jdk150_04\jre\lib\security\cacerts(実際には、使用しているオペレーティング・システムやインストール設定によって異なる可能性があるので注意してください。)
この
cacertsファイルに、クライアントの証明書をインポートする必要があります。
まず、クライアントの証明書を
client_keystore.jksからエクスポートします。次のコマンドを、鍵の生成時に開いたコマンド・シェル・ウィンドウで入力します。
keytool -export -alias client_key -file client_cert.der -keystore C:\client_keystore.jks -storepass abc123
次に、エクスポートした証明書をサーバーのトラストストアにインポートします。次のコマンドを入力します。
keytool -import -alias client_key -file client_cert.der -keystore C:\bea\JDK150~1\jre\lib\security\cacerts
(使用している
cacertsファイルが
C:\bea\JDK150~1\jre\lib\security\以外の場所にある場合は、このコマンドに実際の場所を代入してください。)
プロンプトが表示されたら、パスワードとして
changeitを入力します。(
changeitは、WebLogic Serverで提供されているデフォルトのパスワードです。当然のことですが、本番環境ではより安全性の高いパスワードに変更してください。)
パスワードを入力すると、
Trust this certificate [no]:というプロンプトが表示されます。
yと入力して、[Enter]を押します。
すべてが正常の場合、メッセージ
Certificate was added to keystoreが表示されます。
これで、クライアントの証明書は、サーバーのトラストストアに追加されました。これにより、サーバーでのクライアントの認証にこの証明書が使用されるようになります。
ここまでの手順で
keytoolコマンドを実行していたコマンド・シェル・ウィンドウを終了します。
クライアントの鍵の作成は完了しているので、この鍵が実際にメッセージの整合性を実現するうえでどのように機能するのかを確認することができます。送信さ れたSOAPメッセージに証明書が添付されていれば、受信者はその証明書を確認することで送信者を確実に識別するとともに、メッセージが送信中に変更され ていないかどうかを確認することができます。これを実現するには、クライアント側からSOAPリクエストとあわせて証明書が送られるようにします。(送信 された証明書には、そのメッセージについてのハッシュされたダイジェストも含まれています。改ざんされたメッセージはハッシュが異なっているため、サー バーによる検出が可能となります。これが、メッセージの整合性を保証する仕組みです。)
クライアントから証明書が送られるようにするには、次の2つの変更が必要となります。
サービスにおいてメッセージの整合性が要求されるようにする設定は、非常に簡単です。ソース・コードにアノテーションを1つ追加するだけで設定は完了します。これにはまず、WTPに戻り、
HelloWorldService.javaを開きます。以下のスクリプトで
ハイライトされているコードを追加します。
import javax.jws.WebService;
import weblogic.jws.Policies;
import weblogic.jws.Policy;
@WebService(name = "HelloWorldPortType",
serviceName = "HelloWorldService",
targetNamespace = "http://mycompany.com")
@Policies({
@Policy(uri="policy:Sign.xml")
})
public class HelloWorldService {
public String sayHello(String name) {
return "Hello there, " + name;
}
}
ここまでに実行したのは、1つのアノテーション(
@Policies/@Policy) を追加し、要求された適切なパッケージをインポートしたことだけです。このアノテーションにより、生成したWebサービスでは常に、SOAPリクエストへ の署名を求める要求がクライアントに対して出されるようになります。変更を保存します。このコードにはエラーはないはずです。
ここで、サービスを再生成する必要があります。前述の手順でサービスを生成したときに開いた同一のコマンド・シェルを使用して、コマンド
antを実行します。メッセージ
BUILD SUCCESSFULが表示されます。これにより、サービスの生成、パッケージ化、デプロイがおこなわれます。
サービスが変更されたため、クライアントのプロキシも、新しく生成されたWSDLファイルに基づいて再生成する必要があります。ブラウザに戻り、WSDLファイルのURLをもう一度開きます。そのURLを
WORKSPACE_DIR\WSTest\HelloWorldService.wsdlとして保存します。テキスト・エディタを使用して、このファイルを確認します。
wssp:Integrityという要素があり、そこにそのサービスに関する信頼情報の内容をはじめ、いくつかの情報が含まれています。
wssp:TokenIssuer要素の本体をくまなく探すと、
CN=Client, OU=WEB AGE, C=USへの参照が見つかります。これは、クライアントの鍵を生成してWebLogic Serverのトラストストアにインポートしたときの実際の情報です。また、このWSDLファイルには、以下に示す新しいSign.xmlポリシーも含まれています。
<wsp:Policy s0:Id="Sign.xml">
...
</wsp:Policy>
...
<portType name="HelloWorldPortType" wsp:PolicyURIs="#Sign.xml">
..
</portType>
このWSDLファイルの最後に近い部分に、次のような行があります。
<s2:address location="http://localhost:7001/HelloWorldService/HelloWorldService"
以下のように、この行のポート番号を7002に変更します。
<s2:address location="http://localhost:
7002/HelloWorldService/HelloWorldService"
なぜ、このような変更をおこなうのでしょうか。理由は単純です。クライアントのリクエストがモニター・ポートであるポート7002でおこなわれるようにする必要があるためです。WSDLを保存して終了します。
コマンド・シェル・ウィンドウから、コマンド
ant -buildfile gen-client.xmlを実行します。
BUILD SUCCESSFULメッセージが表示されます。これで、クライアントのプロキシが生成されました。
WTPに戻り、
WSTestプロジェクトをリフレッシュします。ここで、
HelloWorldClientクラスをもう一度実行します。何が起こるでしょうか。ここでは、例外が発生します。表示されたスタック・トレースを確認し、メッセージ
Failed to add Signatureを探します。このメッセージは、サービスから証明書が要求されているにもかかわらず、クライアント側が添付していないことを示しています。これに対しては、クライアントのコードを変更し、サービスを呼び出す場合に証明書が確実に追加されるようにする必要があります。
WTPで
HelloWorldClient.javaを開きます。以下の
import文を追加します。
import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.List; import javax.xml.rpc.Stub; import weblogic.security.SSL.TrustManager; import weblogic.wsee.security.bst.ClientBSTCredentialProvider; import weblogic.wsee.security.unt.ClientUNTCredentialProvider; import weblogic.xml.crypto.wss.WSSecurityContext; import weblogic.xml.crypto.wss.provider.CredentialProvider;
次に、以下のスクリプトで
ハイライトされているコードを
mainメソッドに追加します。
public static void main(String[] args) throws Throwable {
com.test.client.HelloWorldService service = new HelloWorldService_Impl();
HelloWorldPortType port = service.getHelloWorldPortTypeSoapPort();
List credProviders = new ArrayList();
CredentialProvider cp = new ClientBSTCredentialProvider(
"C:\\client_keystore.jks", "abc123", "client_key",
"client_key_password");
credProviders.add(cp);
Stub stub = (Stub) port;
stub._setProperty(
WSSecurityContext.CREDENTIAL_PROVIDER_LIST,
credProviders);
stub._setProperty(
WSSecurityContext.TRUST_MANAGER, new TrustManager() {
public boolean certificateCallback(X509Certificate[] chain,
int validateErr) {
// Put some custom validation code in here.
// Just return true for now
return true;
}
});
String greeting = port.sayHello("Gary");
System.out.println("The greeting returned was: " + greeting);
}
コードを保存します。このコードにはエラーはないはずです。ここで、更新した
HelloWorldClientクラスを実行します。前述の手順の場合と同様に、そのサービスで正常に処理がおこなわれたことを示すメッセージがコンソールに表示されます。ただし、その裏側では別のことが起こっています。WTPに戻ると、
TCP/IP Monitorビューが表示されています。このビューでクリックし、
Requestペインと
Responseペインの両方がXMLで表示されるように変更します。
Requestペインの内容を確認すると、送信SOAPリクエスト・メッセージとしてクライアントが送信した内容が表示されています。送信したメッセージには、セキュリティ・ヘッダー(
wsse:Security)が追加されています。このヘッダーには、クライアントの証明書(
wsse:BinarySecurityToken、
dsig:KeyInfoにより参照)と、 実際のSOAP本文(
dsig:DigestValue)に対する一方向ハッシュ(メッセージ・ダイジェスト)、そしてデジタル署名(
dsig:Signature)が含まれています。メッセージには署名がつけられています。このメッセージにはダイジェスト(
dsig:DigestValue)が含まれていますが、これは実際のSOAP本文についての暗号化された一方向ハッシュです。
同様に、Responseペインについても確認します。サーバーから返されたメッセージにも証明書が添付されています。これはサーバーの証明書です。なぜサーバー側の証明書も追加されているのでしょうか。 サービス・クラスにアノテーションをつけたときにポリシー
Sign.xmlを指定しましたが、このポリシーでは受信と送信の両方に署名をつけることがデフォルトで指定されているため、ここでもクライアントとサーバーの両サイドの署名が交換されたのです。
署名が交換されたことで、どちらのサイドでも、メッセージが本当に指定された送信者からのものであることを確認すると同時に、ダイジェストを確認することでメッセージに改ざんされている部分がないことを立証できます。これで、
メッセージの整合性が実現しました。 ただし、メッセージの
soapenv:bodyはまだプレーン・テキストのままとなっており、メッセージの機密保護(暗号化)はおこなわれていません。実際、SOAPリクエストの
<name>要素とSOAPレスポンスの<return>要素は、誰が見ても明らかな形で表示されています。