この連載の以前の記事では、WSDL サービス記述の中で WS-SecurityPolicy を使用して WS-Security 処理を構成する方法を説明しました。WS-SecurityPolicy の欠点は、例えばアサーションを誤った場所で使用したり、1 つの文書内にアサーションのバージョンを混在させたりするなど、ポリシーを誤って構成しやすいことです。多くの Web サービス・スタックは、このような誤りを黙って無視します。そのため、ポリシーが意図するように機能しない結果となっても、何が誤っているのかわからないまま、ポリシーを調べてその原因を突き止めようとしなければなりません。
今回の記事の前半では、WS-SecurityPolicy を扱うのが難しい理由を説明し、WS-SecurityPolicy で発生しがちなエラーの原因を調べます。後半では、WS-SecurityPolicy 文書の構造検証をサポートする WS-Policy および WS-SecurityPolicy のデータ・モデルを紹介し、JiBX データ・バインディングによって文書をモデルにアンマーシャルする方法を説明します。
「WS-Policy について理解する」では、ポリシー・アサーションを WSDL 1.1 のコンポーネントに関連付ける方法を含め、WS-Policy が持つ側面の大部分を詳しく説明しました。簡単に言うと、WS-Policy でポリシーを WSDL サービス定義に関連付けることのできるポイントはいくつかあり、これらのポイントはメッセージおよびメッセージのグループに対応します。WS-Policy がメッセージをグループ化するレベルには、以下の 4 つがあります。
- メッセージ - ポリシーは特定のメッセージに適用されます (ポリシーが
<wsdl:message>要素によって関連付けられている場合、該当するメッセージが使用されるときには常にポリシーが適用されます。あるいは、<wsdl:portType>または<wsdl:binding>要素の中で、操作の入力/出力/エラーの定義を通じてポリシーが関連付けられている場合は、特定の操作がそのメッセージを使用するときに、ポリシーが適用されます)。 - 操作 - ポリシーは、特定の操作でのすべてのメッセージ交換に適用されます (ポリシーは
<wsdl:binding>または<wsdl:portType>に含まれる<wsdl:operation>要素によって関連付けられます)。 - エンドポイント - ポリシーは、特定のサービス・バインディングに関するすべてのメッセージ交換に適用されるか (ポリシーは
<wsdl:port>または<wsdl:binding>によって関連付けられます)、特定のポート・タイプに基づくすべてのサービス・バインディングに関するすべてのメッセージ交換に適用されます (ポリシーはその<wsdl:portType>に関連付けられます)。 - サービス - ポリシーは、サービスに関連するエンドポイントおよび操作のすべてに適用されます (ポリシーは
<wsdl:service>要素で関連付けられます)。
あるメッセージ・グループ・レベルで適用したポリシーは、その下位の層に継承されるため、各メッセージに適用される実際のポリシー (WS-Policy の用語で言うと、実効 (effective) ポリシー) は、メッセージ、操作、エンドポイント、そしてサービスの各層で適用されるすべてのポリシーの結合になります。したがって、ポリシーはメッセージそのものだけで決まるのではなく、そのメッセージが使用されるコンテキストにも依存します。
「WS-Policy について理解する」でも説明したように、WS-Policy アサーションを WSDL のコンポーネントに関連付ける最も一般的な方法は、<wsp:PolicyReference> 要素を使用することです。大抵の目的には、このような参照を使った関連付けが便利ですが、これしか方法がないわけではありません。代わりに、メッセージ・グループ・レベルのいずれかに対応する WSDL 要素で wsp:PolicyURI という特殊な属性を使用し、要素に適用するポリシー・アサーション URI のリストを属性の値として指定することができます。また、ポリシー・アサーションを直接 WSDL 要素に組み込んで WSDL 要素に関連付けることもできます。
どの方法を使ってポリシー・アサーションを関連付けるかは、ほとんど好みの問題です。元の WSDL スキーマに従って拡張要素を使用できない一部の WSDL 要素では、wsp:PolicyURI 属性を使用することになるはずですが、最近の WS-I WSDL スキーマではあらゆる WSDL 要素で拡張要素を使用できるようになっています。したがって、大抵の Web サービス・スタックでは、拡張要素を使用できるかどうかはポリシー・アサーションを関連付ける方法を決定する上でのポイントにはなっていません。<wsp:PolicyReference> 要素または wsp:PolicyURI 属性のいずれかを使用するとしたら、ポリシー・アサーションを再利用することができます。これには、外部ポリシー・アサーションも含まれます (ただし、すべての Web サービス・スタックが外部参照をサポートするわけではありません)。
さらに、<wsp:PolicyAttachment> 要素を使用して、ポリシーを外部で関連付けるという方法もあります。この方法は、理論上 WSDL の外部でポリシーをサービスに関連付けることができます。しかし、ほとんどの Web サービス・スタックは、このようなタイプの関連付けを処理するようには作られておらず、WSDL サービス記述によってポリシー情報が参照されること、あるいは WSDL サービス記述にポリシー情報が直接組み込まれていることを要求します。
<wsp:PolicyReference> を使用する場合、オプション属性を使用して参照先ポリシーのダイジェスト・アルゴリズムとダイジェスト値を定義することができます。ダイジェストは、外部ポリシー参照にとってある程度のセキュリティーになりますが、参照自体への変更を防ぐわけではありません。セキュリティーに関しては <wsp:PolicyAttachment> のほうが進んでいて、WS-Security を直接使用して関連付け全体をセキュアにすることができるようになっています。
WS-SecurityPolicy の関連付けに関する制限事項
WS-SecurityPolicy が指定するメッセージ・グループ・レベルでは、それぞれのレベルによって、サービス記述に関連付けるポリシー・アサーションのタイプが変わってきます。
- セキュリティー・バインディング・アサーション:
<sp:TransportBinding>は、エンドポイント・レベルでのみ関連付けます (トランスポートはエンドポイントにアクセスするために使用されるため)。<sp:SymmetricBinding>および<sp:AsymmetricBinding>は、エンドポイントまたは操作のいずれかのレベルで関連付けます。 - サポート・トークン・アサーション (
<sp:SupportingTokens>、<sp:SignedSupportingTokens>をはじめ、あらゆる形でのサポート・トークン): エンドポイント、操作、またはメッセージのレベルで関連付けます。 - オプション・アサーション (
<sp:Wss10>、<sp:Wss11>、<sp:Trust13>): エンドポイントのレベルで関連付けます。 - 保護アサーション (
<sp:SignedParts>、<sp:SignedElements>、<sp:EncryptedParts>、<sp:EncryptedElements>、<sp:ContentEncryptedElements>、<sp:RequiredElements>、および<sp:RequiredParts>): メッセージのレベルでのみ関連付けます。
上記のレベルは単なる提案であり、Web サービス・スタックが別のレベルでの関連付けを処理できる場合もあります。けれども、できるだけこれらの提案に従うことが無難です。
上記に挙げていない WS-SecurityPolicy アサーションは他のアサーション内にネストされるため、そのアサーションを収容するアサーションの場所によって、どのレベルに関連付けるかが決まります。
WS-SecurityPolicy で使用する複雑で混乱しやすい構造は、組み立てるのも、直接修正するのも容易ではありません。大抵の商用 Web サービス・スタックには、WS-SecurityPolicy 文書を生成するために使用できる GUI ツールがオプションのメニューに用意されています (オープンソースの Metro スタックでも、NetBeans の一部としてこのようなツールを提供しています)。このようなツールからの出力は、そのツールを提供する Web サービス・スタック自体では上手く機能するはずですが、ベスト・プラクティスの形ではない出力であったり、完全に正確ではない出力であったりする可能性があります。この類のツールを使用しない場合や、ツールでサポートされていないセキュリティー構成を調整したい場合、あるいは単に簡潔なポリシーにしたいという場合には、WS-SecurityPolicy の複雑さに真っ向から立ち向かわなければなりません。
私は GUI WS-SecurityPolicy 構成ツールをどうしても好きになれないので、この連載ではポリシー文書を直接処理してきました。各種のセキュリティー構成をテストするなかで、私は自分が犯したいくつもの間違いによって、WS-SecurityPolicy が複雑であること、そして構成を台無しにしかねない多くの落とし穴があることを絶えず思い知らされてきました。このセクションでは最初に WS-SecurityPolicy が複雑であると思う理由を説明した後、私の経験から、正確で相互運用可能な WS-SecurityPolicy 構成を開発する妨げになりがちな問題を取り上げます。
WS-SecurityPolicy 1.2 スキーマが定義している約 140 の要素は、そのほとんどすべてがグローバル要素として定義されています (グローバル要素とは、文書の他の要素に組み込まれるのではなく、理論上は要素のそれぞれが単独で使用されることを意味します)。グローバル要素の大半は空のマーカー要素として定義され、他の名前空間からの拡張属性を設定できるものの、その要素は空でなければなりません。空のマーカー要素ではない要素のほとんどすべては、WS-SecurityPolicy 名前空間以外の名前空間からの拡張属性および要素の両方を持てるように定義されています。WS-SecurityPolicy 1.3 スキーマは、1.2 スキーマを拡張して要素を少し追加しているに過ぎません。
ベースとなっている 1.2 スキーマとその拡張である、1.3 スキーマは、いずれも実際的な用途には役立ちません。これは、このスキーマの作成者たちだけの責任ではなく、XML Schema そのものにある限界、そして WS-Policy の構造によるものです。
「WS-Policy について理解する」で説明したように、WS-Policy は、ポリシー・アサーションを定義して組み合わせるための単純なスケルトンでしかありません、アサーションそのものは WS-SecurityPolicy などの拡張機能によって定義され、拡張定義のそれぞれが固有の名前空間を使用します。リスト 1 は、WS-SecurityPolicy 文書の一例です。このリストでは、WS-Policy 要素を太字で示しています。
リスト 1. WS-SecurityPolicy の例
<wsp:Policy wsu:Id="SecureConv"
xmlns:wsu="http://.../oasis-200401-wss-wssecurity-utility-1.0.xsd"
xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"
xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702">
<wsap:UsingAddressing xmlns:wsap="http://www.w3.org/2006/05/addressing/wsdl"/>
<sp:SymmetricBinding>
<wsp:Policy>
<sp:ProtectionToken>
<wsp:Policy>
<sp:SecureConversationToken sp:IncludeToken=
"http://.../ws-securitypolicy/200702/IncludeToken/AlwaysToRecipient">
<wsp:Policy>
<sp:RequireDerivedKeys/>
<sp:BootstrapPolicy>
<wsp:Policy>
<sp:AsymmetricBinding>
<wsp:Policy>
<sp:InitiatorToken>
<wsp:Policy>
<sp:X509Token sp:IncludeToken=
"http://.../ws-securitypolicy/200702/IncludeToken/AlwaysToRecipient">
<wsp:Policy>
<sp:RequireThumbprintReference/>
</wsp:Policy>
</sp:X509Token>
</wsp:Policy>
</sp:InitiatorToken>
<sp:RecipientToken>
<wsp:Policy>
<sp:X509Token sp:IncludeToken=
"http://.../ws-securitypolicy/200702/IncludeToken/AlwaysToInitiator">
<wsp:Policy>
<sp:RequireThumbprintReference/>
</wsp:Policy>
</sp:X509Token>
</wsp:Policy>
</sp:RecipientToken>
<sp:AlgorithmSuite>
<wsp:Policy>
<sp:TripleDesRsa15/>
</wsp:Policy>
</sp:AlgorithmSuite>
<sp:Layout>
<wsp:Policy>
<sp:Strict/>
</wsp:Policy>
</sp:Layout>
<sp:IncludeTimestamp/>
<sp:OnlySignEntireHeadersAndBody/>
</wsp:Policy>
</sp:AsymmetricBinding>
<sp:SignedParts>
<sp:Body/>
<sp:Header Namespace="http://www.w3.org/2005/08/addressing"/>
</sp:SignedParts>
<sp:EncryptedParts>
<sp:Body/>
</sp:EncryptedParts>
<sp:Trust13>
<wsp:Policy>
<sp:MustSupportIssuedTokens/>
<sp:RequireClientEntropy/>
<sp:RequireServerEntropy/>
</wsp:Policy>
</sp:Trust13>
</wsp:Policy>
</sp:BootstrapPolicy>
</wsp:Policy>
</sp:SecureConversationToken>
</wsp:Policy>
</sp:ProtectionToken>
<sp:AlgorithmSuite>
<wsp:Policy>
<sp:Basic128Rsa15/>
</wsp:Policy>
</sp:AlgorithmSuite>
<sp:Layout>
<wsp:Policy>
<sp:Strict/>
</wsp:Policy>
</sp:Layout>
</wsp:Policy>
</sp:SymmetricBinding>
<sp:EncryptedParts>
<sp:Body/>
</sp:EncryptedParts>
</wsp:Policy>
|
リスト 1 のポリシー文書は、主に WS-Policy 要素 (この例では<wsp:Policy> 要素) と WS-SecurityPolicy 要素 (接頭辞 sp を使用している要素) が交互に重なる層で構成されています。残念ながら、XML Schema ではこのような構造を表現することはできません。各スキーマ定義が対処するのは、1 つの名前空間のみです。また、別の名前空間に定義された要素を参照および使用することはできても、これらの要素のサブ構造を定義することはできません。つまり、WS-Policy のスキーマ定義で一般構造を定義し、その構造と組み合わせて使用するための要素を WS-SecurityPolicy スキーマで定義することはできますが、この二組の要素間での対話を定義する手段はないということです。
XML Schema は文書の構造を定義できないため、WS-SecurityPolicy 標準のテキストは WS-Policy 勧告と同じ構文表現を使用して、文書に意図された構造を表します。リスト 2 は、WS-SecurityPolicy 標準から抜粋した、リスト 1 で使用した <sp:SymmetricBinding> 要素の構文の例です。
リスト 2. WS-SecurityPolicy 構文の例
<sp:SymmetricBinding xmlns:sp="..." ... >
<wsp:Policy xmlns:wsp="...">
(
<sp:EncryptionToken ... >
<wsp:Policy> ... </wsp:Policy>
</sp:EncryptionToken>
<sp:SignatureToken ... >
<wsp:Policy> ... </wsp:Policy>
</sp:SignatureToken>
) | (
<sp:ProtectionToken ... >
<wsp:Policy> ... </wsp:Policy>
</sp:ProtectionToken>
)
<sp:AlgorithmSuite ... > ... </sp:AlgorithmSuite>
<sp:Layout ... > ... </sp:Layout> ?
<sp:IncludeTimestamp ... /> ?
<sp:EncryptBeforeSigning ... /> ?
<sp:EncryptSignature ... /> ?
<sp:ProtectTokens ... /> ?
<sp:OnlySignEntireHeadersAndBody ... /> ?
...
</wsp:Policy>
...
</sp:SymmetricBinding>
|
リスト 2 で使用している構文は、ほとんどの開発者にとってかなり簡単に理解できるはずです。この構文では、グループを括弧で区切り、| 文字で代替となる選択肢を示し、? でオプションのコンポーネントを示しています (完全な構文では、ゼロまたは複数のオカレンスを示す *、1 つまたは複数のオカレンスを示す + も使用します)。けれどもこれは XML 文法の標準ではないので、この構文をそのまま使用してインスタンス文書を検証することはできません。
WS-SecurityPolicy の複雑さ、そして文書を標準に照らし合わせて確認するのが難しいことを考えれば、文書を処理する際に最も起こりがちな問題の 1 つは当然、誤った箇所にアサーションを追加してしまうことです。例えば、<EncryptedParts> 要素 (リスト 1 の終わり近くにあります) をその前にある <SymmetricBinding> 要素の Policy 内に移動させると、WS-SecurityPolicy 標準に適合しない構造になってしまいます。このように誤って構造化されたポリシーの解釈は未定義のままとなり、それぞれの Web サービス・スタックがどう解釈するかに任されることになります。そうなると、あるスタックでは期待どおりに機能するとしても、他のスタックとは相互運用できない可能性があります。
Web サービス・スタックは、構造のエラーをユーザーにレポートすることもあれば、レポートしないこともあります。上記の EncryptedParts 要素が誤った場所に配置された例を、この連載の以前の記事で評価した 3 つのオープンソース Web サービス・スタックの最新バージョン (Apache Axis2 1.5.4、Metro 2.1、および Apache CXF 2.3.3) で試してみたところ、エラーをレポートして操作を拒否したのは Metro だけでした。Axis2 と CXF は両方ともエラーを出さずにポリシーを受け入れましたが、メッセージ本体を暗号化せずに操作しました。このような暗黙の失敗は、ポリシー構造のエラーによって引き起こされた問題の診断を難しくします。
名前空間は、WS-Policy と WS-SecurityPolicy とでは特に手強い問題となります。WS-Policy と WS-SecurityPolicy の標準化にはいずれも数年かかったことから、標準がリリースされるよりも前に、この 2 つの技術の早期バージョンが広く使用されるようになっていたからです。正式な標準は主として早期バージョンの XML 構造に従っていますが、早期バージョンと正式な標準とでは使用する名前空間が異なるため、ある特定の文書にどちらのルール・セットが適用されているのかは明らかです。
大部分のツールは公式勧告と早期バーションの WS-Policy および WS-SecurityPolicy を両方ともサポートし、どちらのバージョンでも同じように使用できるようになっています (ただし、Axis2 は例外です。Axis2 は現行の 15.4 リリースの時点ではサブミッション・バージョンの WS-Policy しかサポートしていません)。こうした柔軟性はあるものの、1 つの文書で複数の異なる名前空間を使用すると、問題が発生する可能性があります。WS-Policy や WS-SecurityPolicy の名前空間を故意に混在させる理由はありませんが、他のポリシーの一部を組み合わせて新しいポリシーを作成するときには、名前空間をうっかり混在させてしまいがちです。その結果、要素に使用する名前空間の競合が発生したり、トークンを含む値の場合には実際のテキスト値の競合が発生したりする場合があります。例えば、メッセージには常にトークンを含めるように指定する場合、WS-SecurityPolicy のバージョン 1.2 と 1.3 はどちらも値 http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702/IncludeToken/Always を使用しますが、それよりも前のバージョン 1.1 では、同じ目的で値 http://schemas.xmlsoap.org/ws/2005/07/securitypolicy/IncludeToken/Always を使用します。
これらの標準では、バージョンが変わっても XML 要素の意味はほとんど変わらないため、個々のツールはポリシー文書を処理する際に名前空間の違いを無視するという選択をすることができます。この手法を採るツールは、エラーを出すことなく、名前空間が混在したポリシーを受け入れるでしょう。ただし通常は、公式勧告の名前空間だけを使用し、特に以前の名前空間のコンポーネントを勧告の名前空間のコンポーネントと併せて使用しないようにすることが最善の策です。
WS-Policy と WS-SecurityPolicy のモデル化
WS-Policy と WS-SecurityPolicy を Java 言語でモデル化する場合、「WSDL 1.1 を理解してモデル化する」で説明した WSDL 1.1 のモデル化に伴う難しさとはまったく異なる難しさがあります。WSDL をモデル化する際に核心となるのは、一般的な使用において要素の順序のさまざまなバリエーションを考慮することです。しかし WS-Policy と WS-SecurityPolicy の場合、最上位の要素にさまざまなルールを記述するものの、構造については厳密に規定せず、要素の順序は完全に無視します。これはつまり、<wsp:Policy> 要素 (または、他の WS-Policy オペレーターのあらゆるバリエーション) に想定される内容は、要素のコンテキストに完全に依存することを意味します。このことはリスト 1 を見ると明らかで、文書を構成する 15 の <wsp:Policy> 要素のほぼすべてに、異なる内容のモデルが設定されています。
WS-SecurityPolicy アサーションには、大きく分けて 2 つのタイプがあります。1 つは、その存在そのものによって何らかの性質や機能を示すマーカー・アサーション (空の要素)、そしてもう 1 つは構造化アサーションで、後者のタイプのアサーションには他のアサーションを含むポリシーがネストされます。もちろん、複雑さの原因となるのは構造化アサーションのほうです。
構造化アサーションの処理は、モデルの NestedPolicy 基底クラスおよび関連する VerificationHandler インターフェースに基づいて行われます (リスト 3 を参照)。
リスト 3.
NestedPolicy と VerificationHandler
public abstract class NestedPolicy extends AssertionBase {
/** Nested policy definition. */
private Policy m_policy;
/** Arbitrary extension elements. */
private List<Element> m_extensions = new ArrayList<Element>();
...
/**
* Create a handler instance for a set of assertions.
*
* @return instance
*/
public abstract VerificationHandler createHandler();
/**
* Verify policy goodness. This uses a handler supplied by the {@link
* #createHandler()} method to verify all the assertions within the nested policy in
* the context of this assertion.
*
* @param vctx
*/
public void verify(ValidationContext vctx) {
for (Set<AssertionBase> asserts : m_policy.getAlternatives()) {
VerificationHandler handler = createHandler();
for (AssertionBase asser : asserts) {
if (asser instanceof ExtensibleMarker) {
handler.addMarker((ExtensibleMarker)asser, vctx);
} else {
handler.addGeneral(asser, vctx);
}
}
handler.complete(vctx);
}
}
}
public interface VerificationHandler
{
/**
* Add marker assertion.
*
* @param marker
* @param vctx
*/
public abstract void addMarker(ExtensibleMarker marker, ValidationContext vctx);
/**
* Add general assertion (anything that's not a marker).
*
* @param asser
* @param vctx
*/
public abstract void addGeneral(AssertionBase asser, ValidationContext vctx);
/**
* Check that the assertions included in this collection fulfill all requirements for
* the policy. This method is only used when verifying a complete policy (one
* particular combination of alternatives, when using alternatives).
*
* @param vctx
* @return <code>true</code> if no errors, <code>false</code>
* if error
*/
boolean complete(ValidationContext vctx);
}
|
すべての構造化アサーションは NestedPolicy クラスを継承し、特定のアサーション構造に応じてカスタマイズされた VerificationHandler インターフェースのインスタンスを返す createHandler() メソッドを実装します。VerificationHandler は、ネストされたポリシーの中に含まれるアサーションを累積および検証するためのメソッドを提供します。つまり VerificationHandler に、構造化アサーション処理の詳細が実装されます。代替ポリシーがある場合には、NestedPolicy.verify() メソッドは代替ポリシーごとに個別の VerificationHandler インスタンスを作成します。
上記の内容を具体的に示す例を、リスト 4 とリスト 5 にそれぞれ、SymmetricBinding アサーションを表現するクラス、検証するクラスという形で記載します。リスト 4 の SymmetricBinding クラスはかなり単純なもので、リスト 5 に記載する検証を処理するための BindingAssertionHandler クラスを基底クラスとする内部クラスを定義します。
リスト 4.
SymmetricBinding
public class SymmetricBinding extends NestedPolicy {
public VerificationHandler createHandler() {
return new AssertionHandler();
}
private static class AssertionHandler extends BindingAssertionHandler
{
/** Containing element name. */
private static final String ELEMENT_NAME = "SymmetricBinding";
/** Names of allowed token role elements. */
private static final Set<String> TOKEN_ROLES =
VerificationHandlerBase.buildNameSet("EncryptionToken|...|ProtectionToken");
protected AssertionHandler() {
super(ELEMENT_NAME, TOKEN_ROLES,
BindingAssertionHandler.ENCRYPTION_BINDING_MARKERS);
}
public boolean complete(ValidationContext vctx) {
boolean good = true;
Map<String, TokenProperty> tokens = getRoleTokens();
if (tokens.containsKey("ProtectionToken")) {
if (tokens.containsKey("EncryptionToken")) {
vctx.reportError("sp:ProtectionToken conflicts ...", this);
good = false;
}
if (tokens.containsKey("SignatureToken")) {
vctx.reportError("sp:ProtectionToken conflicts ...", this);
good = false;
}
} else if (!tokens.containsKey("EncryptionToken") &&
!tokens.containsKey("SignatureToken")) {
vctx.reportWarning("No tokens defined for binding", this);
}
return super.complete(vctx) && good;
}
}
}
|
SymmetricBinding.AssertionHandler 内部クラスは、<sp:SymmetricBinding> に定義されたトークン・ロールのセットを定義するだけでなく、VerificationHandler.complete() メソッドも実装します。このメソッドは、少なくとも 1 つのタイプのトークンが存在し、競合するトークンが 1 つもないことをチェックします (<sp:SymmetricBinding> では、<sp:ProtectionToken> をメッセージの署名および暗号化の両方に共用することも、あるいは個別の <sp:EncryptionToken> や <sp:SignatureToken> を使用することもできます)。
リスト 5 に記載する BindingAssertionHandler は、3 つすべてのタイプ (トランスポート、非対称、対称) のバインディング・アサーションの検証を処理するための共通ベースであり、そのそれぞれが 1 つ以上のマーカー・アサーション、1 つ以上のトークン・ロール、必要な <sp:AlgorithmSuite>、およびオプションの <sp:Layout> を定義します (最後の 2 つは、子マーカー・アサーションを持つ一方で、ポリシーはネストされないアサーションになります)。
リスト 5.
BindingAssertionHandler
public class BindingAssertionHandler extends VerificationHandlerBase {
/** Names of marker elements allowed in <TransportBinding>. */
public static final Set<String> TRANSPORT_BINDING_MARKERS =
VerificationHandlerUtils.buildNameSet("IncludeTimestamp");
/** Names of marker elements allowed in ... or <SymmetricBinding>. */
public static final Set<String> ENCRYPTION_BINDING_MARKERS =
VerificationHandlerUtils.
buildNameSet("IncludeTimestamp|...|OnlySignEntireHeadersAndBody");
/** Actual element name. */
private final String m_elementName;
/** Roles allowed for tokens. */
private final Set<String> m_tokenRoles;
/** Token properties for binding. */
private final Map<String,TokenProperty> m_roleTokens;
/** Marker assertions allowed in policy. */
private final Set<String> m_knownMarkers;
/** Marker token assertions. */
private final Map<String,ExtensibleMarker> m_nameMarkers;
...
protected BindingAssertionHandler(String name, Set<String> roles,
Set<String> markers) {
m_elementName = name;
m_tokenRoles = roles;
m_roleTokens = new HashMap<String,TokenProperty>();
m_knownMarkers = markers;
m_nameMarkers = new HashMap<String,ExtensibleMarker>();
}
...
public void addMarker(ExtensibleMarker marker, ValidationContext vctx) {
String name = marker.getName();
if (m_knownMarkers.contains(name)) {
// generate warning for duplicate assertion
VerificationHandlerUtils.checkRepeat(marker, m_nameMarkers, vctx);
} else {
vctx.reportError("Assertion not allowed as child of sp:" + m_elementName,
marker);
}
}
public void addGeneral(AssertionBase asser, ValidationContext vctx) {
if (asser instanceof TokenProperty) {
TokenProperty token = (TokenProperty)asser;
String name = token.getName();
if (m_tokenRoles.contains(name)) {
TokenProperty prior = m_roleTokens.get(name);
if (prior == null) {
m_roleTokens.put(name, token);
} else {
vctx.reportError("Duplicate token ", asser);
}
} else {
vctx.reportError("Token not allowed as child of sp:" + m_elementName,
asser);
}
} else if (asser instanceof AlgorithmSuite) {
...
} else {
vctx.reportError("Assertion not allowed as child of sp:" + m_elementName,
asser);
}
}
public boolean complete(ValidationContext vctx) {
if (m_algorithmSuite == null) {
vctx.reportError("Missing required sp:AlgorithmSuite property", this);
return false;
} else {
return true;
}
}
}
|
リスト 4 とリスト 5 はいずれも、ストリング値から名前のセットを組み立てるために VerificationHandlerUtils.buildNameSet() を使用します。このメソッドは、入力ストリングを | (パイプ) 文字で分割して名前のセットに追加される個々の名前を見つけるため、名前を個別に渡す場合に比べて遥かに簡潔なコードになります。組み立てられた名前のセットは、ネストされたポリシーで許可されるアサーションをチェックするために使用されます。
基本的に同じデータを持つ複数の名前空間を使用すると、XML データ・バインティングに大きな問題が発生します。ほとんどのデータ・バインディング・ツールがこれらの名前空間に対処するには、名前空間ごとに個別のクラスのセットを作成するしか方法はありませんが、JiBX データ・バインティングでは、同じクラスに対して複数のバインディングを使用することができます。各バインティングはそれぞれのクラスに対し、名前は同じながらも異なる名前空間にある要素を使用することができます。
WS-Policy と WS-SecurityPolicy の厳密に規定されていない構造は、データ・バインディングにとっても問題の原因となることがあります。しかしこの場合もやはり、JiBX では多少の作業を加えるだけでデータを手際良く処理することができます。JiBX は、そのままでは XML にバインドできないデータ構造をアンマーシャル (およびマーシャル) するためのユーザー拡張コードをサポートします。私は WS-Policy と WS-SecurityPolicy のデータを処理するために、いくつかのカスタム・アンマーシャラーを使用しましたが、そのうち最も興味深いアンマーシャラーは、あらゆる WS-Policy オペレーターとその子アサーションをアンマーシャルする OperatorUnmarshaller でしょう。リスト 6 に、このアンマーシャラーのコードを記載します。
リスト 6.
OperatorUnmarshaller
public class OperatorUnmarshaller implements IUnmarshaller, IAliasable {
...
public boolean isPresent(IUnmarshallingContext ictx) throws JiBXException {
UnmarshallingContext ctx = (UnmarshallingContext)ictx;
ctx.toTag();
if (PolicyNamespace.LOOKUP.getNamespace(ctx.getElementNamespace()) != null) {
String name = ctx.getElementName();
return "Policy".equals(name) || "ExactlyOne".equals(name) ||
"All".equals(name);
}
return false;
}
public Object unmarshal(Object obj, IUnmarshallingContext ictx) ... {
if (isPresent(ictx)) {
return unmarshalOperator((UnmarshallingContext)ictx);
} else {
return null;
}
}
private OperatorBase unmarshalOperator(UnmarshallingContext ctx) ... {
// create the instance to be returned
NamespaceInfo ns = PolicyNamespace.LOOKUP.
getNamespace(ctx.getElementNamespace());
if (ns == null) {
throw new IllegalStateException("Internal error - ...");
}
Policy policy = Policy.getNestedPolicy(ctx);
PolicyNamespace prior = policy == null ?
null : (PolicyNamespace)policy.checkNamespace(ns);
Policy policy = Policy.getNestedPolicy(ctx);
String name = ctx.getElementName();
OperatorBase operator;
if ("Policy".equals(name)) {
policy = new Policy(policy, ns);
operator = policy;
} else if ("ExactlyOne".equals(name)) {
operator = new OperatorBase.ExactlyOne(ns);
} else {
operator = new OperatorBase.All(ns);
}
// check for namespace conflict
if (prior != null && ns != prior) {
((ValidationContext)ctx.getUserContext()).reportError("Policy namespace " +
ns.getUri() + " conflicts with containing policy namespace " +
prior.getUri(), operator);
}
// track object and handle all attributes
ctx.pushTrackedObject(operator);
operator.readAttributes(ctx);
ctx.nextToken();
// process all child elements
while (ctx.toTag() == IXMLReader.START_TAG) {
if (isPresent(ctx)) {
// unmarshal child policy operator
operator.getChildOperators().add(unmarshalOperator(ctx));
} else {
String uri = ctx.getElementNamespace();
name = ctx.getElementName();
IUnmarshaller unmarshaller = ctx.getUnmarshaller(uri, name);
if (unmarshaller == null) {
// treat unmapped element from known namespace as marker assertion
ns = policy.getNamespace(uri);
if (ns != null) {
operator.getChildAssertions().add
(ExtensibleMarkerUnmarshaller.unmarshal(ctx, ns));
} else {
// just use DOM for element from unknown namespace
...
}
} else {
// unmarshal known child element as policy assertion
Object object = unmarshaller.unmarshal(null, ctx);
if (object instanceof AssertionBase) {
operator.getChildAssertions().add((AssertionBase)object);
} else {
throw new JiBXException("Internal error - child element ...");
}
}
}
}
ctx.nextToken();
ctx.popObject();
return operator;
}
}
|
IUnmarshaller インターフェースが定義するメソッドは、現行の要素の開始タグをアンマーシャラーで処理するかどうかをチェックする isPresent() と、要素からのデータをアンマーシャルする unmarshal() の 2 つだけです。リスト 6 のコードでは、isPresent() メソッドは単に現行の要素の名前空間が WS-Policy バージョンの名前空間と一致するかどうかを調べた後、要素名が 3 つのポリシー・オペレーターのいずれかの名前 (Policy、ExactlyOne、または All) と一致するかどうかを調べます。
unmarshal() も同じく単純なメソッドですが、その理由は単に、このメソッドがすべての処理をunmarshalOperator() メソッドに委任するからに過ぎません。unmarshalOperator() は、ポリシー・オペレーター要素のいずれかに位置しているという前提で、まず始めに適切な WS-Policy 名前空間を使用して対応するオペレーター・クラスのインスタンスを作成します (オペレーターが <wsp:Policy> 要素に含まれている場合、オペレーターで使用されている名前空間が、その要素で使用されている名前空間と一致することを検証します)。次に、ループを実行して子要素のすべてをアンマーシャルします。子要素の処理方法には、以下の 4 通りがあります。
- 子要素が別のポリシー・オペレーターである場合は、再帰的に
unmarshalOperator()を呼び出します。 - この要素のアンマーシャラーがある場合 (つまり、バインディング定義に要素のマッピング定義が含まれる場合) は、そのアンマーシャラーを呼び出します。
- 要素の名前空間がポリシー拡張の名前空間であると認められた場合は、空のマーカー・アサーションとしてアンマーシャルします。
- 上記のどれにも当てはまらない場合は、どの要素にも分類されない拡張要素として扱い、DOM 表現を使用します。
上記の 3 番目の方法は、JiBX バインディング定義でマーカー要素に名前を指定する必要がないことを意味するため、バインディングを比較的単純に維持するのに役立ちます (さらに、個々のクラスも必要ないことから、データ構造も比較的単純になります)。これらのバインディングでは、すべての非マーカー・アサーションに対して JiBX の mapping 定義をする必要がありますが、名前空間にはそれぞれ個別のバインディングを使用しなければなりません。リスト 7 に、WS-Policy と WS-SecurityPolicy に共通の抽象化マッピング (要素名との関連付けはないため、すべての名前空間で再利用できます) を含む最上位レベルのバインディングを記載します。
リスト 7. 最上位レベルのアンマーシャル・バインディング定義
<binding package="com.sosnoski.ws" trim-whitespace="true"
value-style="attribute" force-classes="true" direction="input" track-source="true">
<include path="in-policy-200409.xml"/>
<include path="in-policy-200702.xml"/>
<include path="in-secpolicy-200507.xml"/>
<include path="in-secpolicy-200702.xml"/>
<!-- Base marker element mapping -->
<mapping class="com.sosnoski.ws.policy.ExtensibleMarker" unmarshaller=
"com.sosnoski.ws.secpolicy.SecurityPolicyNamespace$SecurityPolicyMarkerUnmarshaller"/>
<!-- Basic nested policy mapping -->
<mapping abstract="true" class="com.sosnoski.ws.secpolicy.NestedPolicy"
pre-set="preset" ordered="false" allow-repeats="true">
<structure set-method="setPolicy" usage="optional"
unmarshaller="com.sosnoski.ws.policy.OperatorUnmarshaller"/>
<structure type="org.w3c.dom.Element" unmarshaller="org.jibx.extras.DomElementMapper"
set-method="addExtension" usage="optional"/>
</mapping>
...
<!-- Token base mapping -->
<mapping abstract="true" class="com.sosnoski.ws.secpolicy.TokenBase"
ordered="false" allow-repeats="true">
<structure map-as="com.sosnoski.ws.secpolicy.NestedPolicy"/>
<structure name="Issuer" set-method="setIssuer" usage="optional"
unmarshaller="com.sosnoski.ws.policy.ExtensibleValueUnmarshaller"/>
<structure name="IssuerName" set-method="setIssuerName" usage="optional"
unmarshaller="com.sosnoski.ws.policy.ExtensibleValueUnmarshaller"/>
</mapping>
<!-- Token property base mapping -->
<mapping abstract="true" class="com.sosnoski.ws.secpolicy.TokenProperty"
pre-set="preset" ordered="false" allow-repeats="true">
<structure map-as="com.sosnoski.ws.secpolicy.NestedPolicy"/>
</mapping>
<!-- Base handling for protection specifications -->
<mapping abstract="true" class="com.sosnoski.ws.secpolicy.ProtectParts"
pre-set="preset" ordered="false" allow-repeats="true">
<structure name="Body" set-method="setBody" usage="optional"
unmarshaller="com.sosnoski.ws.secpolicy.SecurityMarkerUnmarshaller"/>
<structure name="Attachments" set-method="setAttachments" usage="optional"
unmarshaller="com.sosnoski.ws.secpolicy.SecurityMarkerUnmarshaller"/>
<structure name="Header" set-method="addHeader" usage="optional"
unmarshaller="com.sosnoski.ws.secpolicy.Header$HeaderUnmarshaller"/>
</mapping>
</binding>
|
リスト 7 のバインディングに含まれる <include> 要素が参照する、名前空間のバージョンに固有のバインディングのペアをリスト 8 に記載します。一方は WS-Policy 名前空間のバインディング、もう一方は WS-SecurityPolicy 名前空間のバインディングです。この 2 つのバインティングは名前空間のバージョンに依存しないデータ・モデル・クラスを特定の名前空間の要素名に関連付けると同時に、処理を特定のアンマーシャラー・クラスに渡すか (WS-Policy オペレーター要素の場合は、リスト 6 の OperatorUnmarshaller クラス)、あるいはリスト 7 のバインディングの抽象化マッピングのいずれかに委任します。
リスト 8. WS-Policy および WS-SecurityPolicy のアンマーシャル・バインディング定義
<binding value-style="attribute" force-classes="true" direction="input"
track-source="true">
<!-- Make the recommendation namespace the default -->
<namespace uri="http://schemas.xmlsoap.org/ws/2004/09/policy"
default="elements" prefix="wsp"/>
<!-- Define all supported policy elements -->
<mapping name="Policy" class="com.sosnoski.ws.policy.Policy"
unmarshaller="com.sosnoski.ws.policy.OperatorUnmarshaller"/>
<mapping name="ExactlyOne" class="com.sosnoski.ws.policy.OperatorBase$ExactlyOne"
unmarshaller="com.sosnoski.ws.policy.OperatorUnmarshaller"/>
<mapping name="All" class="com.sosnoski.ws.policy.OperatorBase$All"
unmarshaller="com.sosnoski.ws.policy.OperatorUnmarshaller"/>
<mapping class="com.sosnoski.ws.policy.PolicyReference" name="PolicyReference">
<structure map-as="PolicyReference"/></mapping>
</binding>
<binding value-style="attribute" force-classes="true" direction="input"
track-source="true">
<!-- Make the WS-SecurityPolicy 1.1 namespace the default -->
<namespace uri="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy"
default="elements" prefix="sp1"/>
<!-- Token variations -->
<mapping name="SecureConversationToken"
class="com.sosnoski.ws.secpolicy.SecureConversationToken">
<structure map-as="com.sosnoski.ws.secpolicy.TokenBase"/></mapping>
<mapping name="X509Token" class="com.sosnoski.ws.secpolicy.X509Token">
<structure map-as="com.sosnoski.ws.secpolicy.TokenBase"/></mapping>
...
<!-- Token property variations -->
<mapping name="InitiatorToken"
class="com.sosnoski.ws.secpolicy.TokenProperty$InitiatorToken">
<structure map-as="com.sosnoski.ws.secpolicy.TokenProperty"/></mapping>
<mapping name="InitiatorSignatureToken"
class="com.sosnoski.ws.secpolicy.TokenProperty$InitiatorSignatureToken">
<structure map-as="com.sosnoski.ws.secpolicy.TokenProperty"/></mapping>
<mapping name="InitiatorEncryptionToken"
class="com.sosnoski.ws.secpolicy.TokenProperty$InitiatorEncryptionToken">
<structure map-as="com.sosnoski.ws.secpolicy.TokenProperty"/></mapping>
<mapping name="RecipientToken"
class="com.sosnoski.ws.secpolicy.TokenProperty$RecipientToken">
<structure map-as="com.sosnoski.ws.secpolicy.TokenProperty"/></mapping>
<mapping name="RecipientSignatureToken"
class="com.sosnoski.ws.secpolicy.TokenProperty$RecipientSignatureToken">
<structure map-as="com.sosnoski.ws.secpolicy.TokenProperty"/></mapping>
<mapping name="RecipientEncyrptionToken"
class="com.sosnoski.ws.secpolicy.TokenProperty$RecipientEncyrptionToken">
<structure map-as="com.sosnoski.ws.secpolicy.TokenProperty"/></mapping>
<mapping name="ProtectionToken"
class="com.sosnoski.ws.secpolicy.TokenProperty$ProtectionToken">
<structure map-as="com.sosnoski.ws.secpolicy.TokenProperty"/></mapping>
<mapping name="EncryptionToken"
class="com.sosnoski.ws.secpolicy.TokenProperty$EncryptionToken">
<structure map-as="com.sosnoski.ws.secpolicy.TokenProperty"/></mapping>
<mapping name="SignatureToken"
class="com.sosnoski.ws.secpolicy.TokenProperty$SignatureToken">
<structure map-as="com.sosnoski.ws.secpolicy.TokenProperty"/></mapping>
...
<!-- Define other assertions containing nested policies -->
<mapping name="AlgorithmSuite" class="com.sosnoski.ws.secpolicy.AlgorithmSuite">
<structure map-as="com.sosnoski.ws.secpolicy.NestedPolicy"/></mapping>
<mapping name="AsymmetricBinding" class="com.sosnoski.ws.secpolicy.AsymmetricBinding">
<structure map-as="com.sosnoski.ws.secpolicy.NestedPolicy"/></mapping>
<mapping name="BootstrapPolicy" class="com.sosnoski.ws.secpolicy.BootstrapPolicy">
<structure map-as="com.sosnoski.ws.secpolicy.NestedPolicy"/></mapping>
<mapping name="Layout" class="com.sosnoski.ws.secpolicy.Layout">
<structure map-as="com.sosnoski.ws.secpolicy.NestedPolicy"/></mapping>
<mapping name="SymmetricBinding" class="com.sosnoski.ws.secpolicy.SymmetricBinding">
<structure map-as="com.sosnoski.ws.secpolicy.NestedPolicy"/></mapping>
<mapping name="Trust13" class="com.sosnoski.ws.secpolicy.Trust13">
<structure map-as="com.sosnoski.ws.secpolicy.NestedPolicy"/></mapping>
...
<!-- Other elements with specific handling -->
<mapping name="SignedParts" class="com.sosnoski.ws.secpolicy.ProtectParts$SignedParts"
factory="com.sosnoski.ws.secpolicy.ProtectParts.newSignedParts">
<structure map-as="com.sosnoski.ws.secpolicy.ProtectParts"/></mapping>
<mapping name="EncryptedParts"
class="com.sosnoski.ws.secpolicy.ProtectParts$EncryptedParts"
factory="com.sosnoski.ws.secpolicy.ProtectParts.newEncryptedParts">
<structure map-as="com.sosnoski.ws.secpolicy.ProtectParts"/></mapping>
...
</binding>
|
JiBX を使用するとしても、WS-Policy 文書と WS-SecurityPolicy 文書をアンマーシャルするにはかなりの複雑さを伴います。そうは言っても、バインディング定義にユーザー拡張コードを組み合わせてアンマーシャルすることで、融通の利かない XML 処理手法を採るデータ・バインディング・ツールを使用する場合よりも大幅にこのタスクが容易になることは確かです。
この記事では、WS-SecurityPolicy を理解しにくくしている問題について説明するともに、WS-SecurityPolicy 文書を処理する際に犯しがちな誤りを説明しました。さらに、WS-SecurityPolicy 文書の検証をサポートする、WS-Policy と WS-SecurityPolicy の組み合わせに対応する Java データ・モデルの基本を紹介し、このモデルを組み立てるために JiBX データ・バインディングを使って文書をアンマーシャルする方法を説明しました。
次回の記事では、WSDL と WS-Policy/WS-SecurityPolicy 文書を検証して再フォーマット設定するツールの開発を完結させます。そのなかで、モデルが代替ポリシーやその他の検証の側面を処理する仕組みを詳しく検討するとともに、JiBX データ・バインディングを使用して、現行バージョンの標準フォーマットおよびベスト・プラクティスのフォーマットへと変換された文書を出力する方法を説明します。
学ぶために
- W3C Web Services Policy Working Group: このグループが WS-Policy 仕様を定義しています。最新バージョンの WS-Policy 1.5 は、この仕様に関心を寄せる企業のグループによって開発された仕様案、WS-Policy 1.2 サブミッションをベースとしています。WS-Policy 1.2 サブミッションと WS-Policy 1.5 公式勧告は、両方とも広範に使用されているバージョンです (少なくとも Web サービス・スタックの 1 つ、Axis2 が現在サポートしているのはサブミッション・バージョンのみです)。
- OASIS Web Services Secure Exchange (WS-SX) TC: WS-SecurityPolicy を担当し、現行の WS-SecurityPolicy 1.3 (WS-SecurityPolicy 1.2 をベースに拡張し、共通要素には WS-SecurityPolicy 1.2 名前空間を維持) を開発した組織です。WS-SecurityPolicy の最新バージョンも同じく、この仕様に関心を寄せる企業のグループによって開発された仕様案、WS-SecurityPolicy 1.1 サブミッションをベースとしています。大半のアプリケーションは現在、公式 WS-SecurityPolicy 1.2/1.3 勧告を使用していますが、WS-SecurityPolicy 1.1 サブミッション・バージョンも依然として広く使用されています。
- Technology bookstore で、この記事で取り上げた技術やその他の技術に関する本を探してください。
- developerWorks Java technology ゾーン: Java プログラミングのあらゆる側面を網羅した記事が豊富に用意されています。
製品や技術を入手するために
- Apache Neethi: Neethi は、WS-Policy の処理に使用できるオープンソースのツールの 1 つです。他のほとんどのポリシー・ツールと同じく、Neethi は XML の DOM 表現をベースとします。WS-SecurityPolicy や他の WS-Policy 拡張は直接サポートしていませんが、アプリケーションで拡張することによって、これらのサポートを提供するように設計されています。
- JiBX データ・バインディング: JiBX は、XML 文書から Java データ・モデルへの変換、またはその逆の変換を行うためのツールです。JAXB などの他のデータ・バインディング手法よりも優れたパフォーマンスと柔軟性を提供する JiBX は、単純なスキーマ定義には収まらない複雑な文書構造にとりわけ役立ちます。
議論するために
- developerWorks コミュニティーに参加してください。

Dennis Sosnoski は Java ベースの SOA および Web サービスを専門とするコンサルタント兼トレーナーです。専門家としてのソフトウェア開発経験は 30 年以上に渡り、この 10 年間はサーバー・サイドの XML 技術や Java 技術に注力しています。オープンソースのJiBX XML Data Binding フレームワークや、それに関連した JiBX/WS Web サービス・フレームワークの開発リーダーを務め、さらに Apache Axis2 Web サービス・フレームワークのコミッターでもあります。彼は JAX-WS 2.0 および JAXB 2.0 仕様のエキスパート・グループの一員でもありました。連載「Java Web サービス」の内容は、彼の SOA および Web サービスのトレーニング・クラスがベースとなっています。