ラベル WS-* の投稿を表示しています。 すべての投稿を表示
ラベル WS-* の投稿を表示しています。 すべての投稿を表示

2009年11月28日土曜日

Rampart 1.4/Tomcat v6.0/Eclipse 3.5

Tomcat 上の rampart 1.4 を Eclipse から動かしてみるテスト。 平文のユーザ名とパスワードを含む SOAP メッセージを、HTTPS で保護してみたい。Ant から動かすサンプルがあるけど、敢えてEclipse で動かす事にした。コード類はほぼそのまま。 ■使ったもの
  • Eclispe 3.5 Galileo
  • WTP 3.1.1
  • Axis2 1.4.1 (1.5.1 はダメだった…)
  • Rampart 1.4
  • Rampart policy サンプル下の sample-tomcat
  • もしかしたら、いるかもしれないもの
    • jaxen-1.1.1.jar
    • mex-1.5.1-impl.jar
■ 前提
  • Eclipse WTP で Tomcat v6.0 サーバが構成されている事(Servers ビューに表示されていること。)
  • rampart 1.4 がインストールされているフォルダを{rampart}とする。
  • {rampart}/sample/policy/sample-tomcatを{sample-tomcat}とする。
■ 作り方 ◆ サーバ の構成変更
  • [Project Explorer]/[Servers] 下の Tomcatサーバ構成 (普通は「Tomcat v6.0 Server at localhost-config」的な名前) に着目する。
  • ここに Rampart サンプル内の service.jks をコピーする。中身を見たかったら、コマンドプロンプトで パスの通っている所から keytool -list -v -keystore service.jks と打ち込めば、パスワードを聞いてくるので apache と応えれば、詳しい内容が表示される。
  • service.xml を開いて、以下のように port 8443 の定義を追加する。
    <Connector 
       SSLEnabled="true" 
       clientAuth="false" 
       keystoreFile="conf/service.jks" 
       keystorePass="apache" 
       maxThreads="150" 
       port="8443" 
       protocol="HTTP/1.1" 
       scheme="https" 
       secure="true" 
       sslProtocol="TLS" 
       truststoreFile="conf/service.jks" 
       truststorePass="apache" 
       truststoreType="JKS"/>
  • サーバを [Publish] する。
  • サーバを [Start] して、コンソールに「情報: Coyote HTTP/1.1を http-8443 で起動します」と表示され、他に以上が無いのを確認。
◆ サービスを作る
  • 新規 Dynamic Web Project ウィザードで以下のように指定
    • 適当なプロジェクト名をつける。rampart-trial2とした
    • Configuration -> Modify...->Axis Web Services にチェック
  • {sample-tomcat}のソース(*.javaファイル3つ)を, src 下に[Import]する。
  • WebContent/WEB-INF/lib 下に、{rampart}/lib 下の*.jarをコピーする。
  • WebContent/WEB-INF/modules 下に、{rampart}/modules/rampart-1.4.marをコピーする
  • SimpleService ウェブ・サービス作成
    • プロジェクトの コンテキストメニューから、[New]->[Other]->[Web Service]で、[Next>]押下
    • タイプにBottomUp、インプリメンテーションにSimpleService を設定して、スライダーを AssembleService にあわせる。
    • 「Configuration」の下を見て、Server→Tomcat v6.0、Web service runtime→Axis2、Service project→rampart-trial2になっている事を確認して、[Next>]押下.
    • [Finish]を押下して、WebContent/WEB-INF/services下に SimpleService フォルダが出来ていることを確認
  • SimpleService/META-INF/service.xmlの内容を、{sample-tomcat}/services.xmlの内容で置換。(新規 Web Service のウィザードの中で、既存 services.xml を指定するところがあるけど、上手く動かなかった。)
  • Tomcat サーバに追加して、[Restart]する.
  • ブラウザからhttps://127.0.0.1:8443/rampart-trial2/axis2-admin/を叩いて、admin/axis2 でログインする。
  • [Available Services]を見て、SimpleService が表示されているのを確認。EPR はこんな感じのはず。
    https://127.0.0.1:8443/rampart-trial2/services/SimpleService
  • ついでに WSDL も見て、<wsp:Policy>が入っている事と、他に以上が無いことを確認。
◆ クライアントを作る
  • Client クラスのコードは上でインポート下のをそのまま使う
  • プロジェクト直下に client_repository を以下のように構成する。
    client_repository/
       conf/
       module/
          addressing-1.5.1.mar
          rampart-1.4.mar
  • Client クラスのコンテキストメニューから、[Run As...]/[Run Configuration...] でフォームを開いて、"Java Application" から[New]
    • 「Name」には適当に実行の名前を指定。ここでは 「rampart client」とした。
    • Program Arguments に以下のように指定。
          https://127.0.0.1:8443/rampart-trial2/services/SimpleService
          client_repository
          client_repository/conf/policy.xml
  • もし無かったら、jaxen-1.1.1.jar と、mex-1.5.1-impl.jar を適当に探して、WEB-INF/lib 下に 置いておく。
■ 動かす
  • rampart clientを実行する。Eclipse の Console ビュー にて、以下のような内容のレスポンスが表示されることを確認(本当は一行)。
    <ns:echoResponse xmlns:ns="http://sample.tomcat.rampart.apache.org">
      <ns:return>Hello world</ns:return>
    </ns:echoResponse>
  • クラスパスの通っているところにlog4j.properties を置いておけば、クライアントのログ出力で、リクエストとレスポンスの中身がわかる。ユーザ名とパスワードを平文で送っている事がわかる。
    <?xml version='1.0' encoding='UTF-8'?>
    <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
      <soapenv:Header xmlns:wsa="http://www.w3.org/2005/08/addressing">
        <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" soapenv:mustUnderstand="1">
          <wsu:Timestamp xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="Timestamp-16291471">
            <wsu:Created>2009-11-28T04:49:45.562Z</wsu:Created>
            <wsu:Expires>2009-11-28T04:54:45.562Z</wsu:Expires>
          </wsu:Timestamp>
          <wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="UsernameToken-27667505">
            <wsse:Username>alice</wsse:Username>
            <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">bobPW</wsse:Password>
          </wsse:UsernameToken>
        </wsse:Security>
        <wsa:To>https://127.0.0.1:8443/rampart-trial2/services/SimpleService</wsa:To>
         <wsa:MessageID>urn:uuid:488460AB6C6837F5671259383785225</wsa:MessageID>
        <wsa:Action>urn:echo</wsa:Action>
      </soapenv:Header>
      <soapenv:Body>
        <ns1:echo xmlns:ns1="http://sample.tomcat.rampart.apache.org">
          <param0>Hello world</param0>
        </ns1:echo>
      </soapenv:Body>
    </soapenv:Envelope>
  • localhost をキャプチャできるネットワークモニタで見てみると、ちゃんと暗号化して通信している事も確認できる。
一応、Eclipse + Axis + Rampart を連携させられた。ただ、Axis2 1.5.1 では README 通りの作法でも動かなかったのと、WTP との相性が悪いのもあって、1.4.1 でしか動かせてないのが悩ましい。まあ、実プロジェクトに導入するときのたたき台くらいにはなるかも。

2009年11月27日金曜日

Rampart 1.4 サンプルの観察 (policy編)

前ポストでは、Rampart サンプルのうち basic フォルダ下にあるものを眺めてみた。今回は、policy フォルダにあるものを見てみる。

basic サンプルには client.axis2.xml があって、これがクライアントの挙動を定義していたけど、policy サンプルでは 同じような事を WS-Policy の形式で policy.xml に記述している。service.xml の方にも、この形式でサービスの挙動が書かれている。

また、policy.xml と service.xml への記述以外にも、http://localhost:8080/axis2/services/sample0{1~6}?wsdl を見ると、WSDL にも含まれているのがわかる。更に、sample 06 では、サービスのメタデータとしてもやり取りされているのが観察できる。

policy サンプルを動かすやり方は、基本的に basic サンプルと同様。ただし、sample 05 を動かすために以下の追加作業が必要。
  • backport-util-concurrent.jar (バージョン3.1) が無いので、どこかから落としてきて(ググればすぐ見つかる)%AXIS2_HOME%\lib に置く
  • OpenSAML をここ(→URL)から落として取り替える。
また、sample 05, 06 では echo 以外の複数のサービスを立てているので、TCPMon で観察するには、build.xml の他に Java コードや *.xml に含まれているポート番号も書き換える必要がある。(見ればすぐ分かる)

ちなみにsample 05, 06 では他サンプルと共通の echo サービスのほかに、SAML セキュリティートークンを発行するための sts サービスがあり、さらにsample 06 ではセキュリティポリシーを表示するための mex サービスもある。(この複数サービス間のセキュリティ連携の辺りの観察が、特に面白い。)

以下、サンプルで使われている主な Webサービス/XML仕様。
仕様の名前サンプル番号
WS-Addressing (wsa)01~06
WS-Security (wsse)01~06
WS-Security (wsu)01~06
WS-Security (wsse11)04~06
XML-Signatures (ds)02~06
XML-Encryption (xenc)03~06
前ポスト参照
WS-Policy (wsp)01~06
どんなセキュリティトークンを使うかだとか、サービスのポリシーを定義するフレームワーク。WS-SecurityPolicyや、その他の Web サービス仕様による記述を<wsp:Policy>要素の中に含める形で、組み合わせて使う。この<wsp:Policy>要素は、WSDL、policy.xml、service.xml、また<mex:GetMetadata>リクエストへのレスポンスなどに含まれる事になる。
WS-SecurityPolicy (sp)01~06
WS-Policy フレームワークに組み込まれる形で、ポリシーのセキュリティ関連部分を表現する。
WS-Trust (t/wst)05, 06
このサンプルでは SAML セキュリティトークンのやり取りに使っている。
<wst:RequestSecurityToken>で SAML セキュリティトークンをリクエストすると、 SAML Assertion が乗っかったレスポンスが返ってくる。
WS-SecureConversation (wsc)04
複数のメッセージのやり取りが、トークンが付与されたコンテキストの中で行われるようにして、セキュアな「会話」を実現する仕組み。sample 04 では、3対の echo リクエスト/レスポンスとキャンセル要求を交換している。
SAML1.0 protocol (samlp)05, 06
SAML1.0 assertion (saml)05, 06
SAML の セキュリティトークン。サンプルでは STSサービスから<Assetion> を取得して、これを以降のリクエストに含める形で使用している。
WS-MetadataExchange (mex)06
<mex:GetMetadata> を使って、mex サービスからサービスのポリシー(WS-Policy)を取得する。このポリシーに従って、STS サービスからセキュリティトークン(SAML Assertion)を取得する。
Exclusive XML Canonicalization (ec) 05, 06
<ec:InclusiveNamespaces>を使って名前空間指定の一貫性を守って、XML-Signatures の署名対象が改ざんされたとみなされてしまう事を防止する。

2009年11月25日水曜日

Rampart 1.4 サンプルの観察 (basic)

Web サービスを調べ始めると、SOAP 仕様以外にもたくさん仕様があって、ちょっと戸惑う。で、これらを仕様書ベースで勉強しようと思っても、なんだか良く分からないし、全然面白くも無い。で、WS-*、XML-*という名前だけ知っていて、中身はよく知らないまま面倒くさくて放置してしまったりする。

こういう仕様の場合たいていそうだけど、やはり百聞は一見にしかずというか、実際に現物が動いている様子を観察すると、割と簡単に腑に落ちる事がある。実際にクライアントとサービスの間でやり取りされている生のメッセージを見ると、思ったより簡単な話だったりしてちゃんと納得できる事が多い。

実際にメッセージを流してみるには、Java の場合だと、Axis2 が結構枯れた技術で sample もよく動くので、手っ取り早い。以下、WS-Security 関連仕様を Axis2 Rampart のサンプルを使って、メッセージを観察したメモ。Axis2 Rampart のサンプルは、basic と policy の2群があるけど、今回は、basic 下のサンプルを使ってみた。(サンプルを動かすやり方は、前にここにメモした。)

仕様の名前サンプル番号
WS-Addressing (wsa)全部
HTTP のような下層の転送プロトコルに依存せずに、宛先や返信先などを指定する仕様
WS-Security (wsse)02~10
WS-Security (wsu)02~10
WS-Security (wsse11)09, 10
SOAP メッセージのセキュア化の仕組み。他の仕様(XML Signatures、XML Encryption )と組み合わせて使える。
XML Signatures (ds)04~10
XML 文書に電子署名する仕様
XML Encryption (enc)05,06,09,10
XML 文書の暗号化の仕様
XML-binary Optimized Packaging (xop)10
別の MIME パートとしてSOAP エンベロープの外に出したバイナリデータを参照する仕組み。ここでは暗号化した本文をバイナリで別MIMEパートにしている。

2009年11月20日金曜日

Rampart 1.4/暗号化/クライアント側

前ポストで、暗号化された SOAP メッセージを、サービス側が期待して待つようにした。今度はクライアントに暗号機能を与えてみる。 ■ コード クライアントのリクエスト送信コードは以下のようになる。見ての通りいかにもセキュリティっぽいコードは無い。ただし ConfigurationContext というものを使って、実行時の振る舞いに設定情報を反映させることになる。
import …略…

public class Client {
   private static final String URL 
       = "http://localhost:8888/axis2/services/StockQuoteService";

   private static final String AXIS2_REPO = "resources/axis-repo";

   public static void main(String[] args) throws Exception {
      ConfigurationContext ctx = ConfigurationContextFactory
            .createConfigurationContextFromFileSystem(
               AXIS2_REPO, AXIS2_REPO + "/conf/axis2.xml");

      Options options = new Options();
      options.setAction("urn:getPrice");
      options.setTo(new EndpointReference(URL));

      ServiceClient client = new ServiceClient(ctx, null);
      client.setOptions(options);

      OMElement response = client.sendReceive(getPayload("test"));
      System.out.println(response);
   }
   private static OMElement getPayload(String value) {
       OMFactory factory = OMAbstractFactory.getOMFactory();
       OMNamespace ns = factory.createOMNamespace(
          "http://quickstart.samples/xsd","ns1");
       OMElement elem = factory.createOMElement("getPrice", ns);
       OMElement childElem = factory.createOMElement("symbol", null);
       childElem.setText(value);
       elem.addChild(childElem);

       return elem;
   }
}
main() 冒頭で ConfigurationContext を初期化している AXIS2_REPO はこんな感じ。
axis-repo/
   conf/
      axis2.xml
   modules/
      addressing-1.5.1.mar
      rampart-1.4.mar
modules 下の *.mar は手近などこかから持ってきて配置する。axis2.xml は、サンプルなんかに含まれるものを加工すると楽。ここでは rampart サンプル 05番 の client.axis2.xml を使う事にした。このサンプルには IN と OUT 両方の暗号化設定が記述されているけど、今やりたいのはリクエストだけなので InflowSecurity の記述は削除する。 残った要点はこの部分。
<module ref="rampart" />

<parameter name="OutflowSecurity">
   <action>
      <items>Encrypt</items>
      <encryptionUser>service</encryptionUser>
      <encryptionPropFile>client.properties</encryptionPropFile>
   </action>
</parameter>
rampart モジュールを使う事の宣言と、OutflowSecurity のところで、クライアントからサービス方向のセキュリティ設定が記述されているのがわかる。 上の XMLで <encryptionPropFile>から参照されている client.properties は、以下のような内容で resources 直下に作る。
org.apache.ws.security.crypto.provider=org.apache.ws.security.components.crypto.Merlin
org.apache.ws.security.crypto.merlin.keystore.type=jks
org.apache.ws.security.crypto.merlin.keystore.password=rampart
org.apache.ws.security.crypto.merlin.file=ourkeys.jks
~crypto.merlin.file で指定しているキーストア・ファイル ourkeys.jks は、ここでは手を抜いてサーバと同じものを使う事にした(なまじ真面目にやると、思い通りに動かないときキーの作り方が悪いのか設定の仕方が悪いのか切り分けしにくくて面倒くさい)。この ourkeys.jks も resouces 下に置く。resouces には、お好みで log4j.properties なども置いておく。 ここまでまとめるとこんな感じになる
{作業ディレクトリ}
   resources/
      axis-repo/ … 前述
      client.properties
      log4j.properties
      ourkeys.jks
   src/
      Client.java
      PWCBHandler.java
■ ビルド → 実行 この作業ディレクトリで、こんな要点の build.xml を書く。
  • axis2 の lib ディレクトリ内の *.jar を java タスク と javac タスクのクラスパスに含める。
  • build フォルダを作って、javac で コンパイルした2つのクラスファイルをそこに入れる。
  • resources 下の client.properties、log4j.properties、ourkeys.jks を build 下にコピーする。
  • java タスクでは build/ もクラスパスに含める。
実行する前に、TCPMon (他の何かでもOK)をポート:8888で待たせておく。 で、ant から実行。 上手くいくと、こんな感じのリクエストが送られる。
POST /axis2/services/StockQuoteService HTTP/1.1
Content-Type: text/xml; charset=UTF-8
SOAPAction: "urn:getPrice"
User-Agent: Axis2
Host: 127.0.0.1:8888
Transfer-Encoding: chunked

83c
<?xml version='1.0' encoding='UTF-8'?>
   <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
      <soapenv:Header>
         <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" soapenv:mustUnderstand="1">
            <xenc:EncryptedKey Id="EncKeyId-urn:uuid:7934811BD783B11FAA12585771335152">
               <xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5" />
               <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
                  <wsse:SecurityTokenReference>
                     <ds:X509Data>
                        <ds:X509IssuerSerial>
                           <ds:X509IssuerName>CN=service,OU=a,O=a,L=a,ST=a,C=aa</ds:X509IssuerName>
                           <ds:X509SerialNumber>1258572637</ds:X509SerialNumber>
                        </ds:X509IssuerSerial>
                     </ds:X509Data>
                  </wsse:SecurityTokenReference>
               </ds:KeyInfo>
               <xenc:CipherData>
                  <xenc:CipherValue>KWbsTXPE5TB4KzH9VRe2TJXT51tPDQ3z0UilymTUu165MR/hGB7uHyscSeYB1TEkA+23jrx3S9uo+nFahrBOuMOUsUvTckYvmi66K/0GLP8QAjg0gubiFmxF6d3JjI5mNuaGHWWspplKxEAFS99Xp4MQTXO52Gn/qo99d+vibzg=</xenc:CipherValue>
               </xenc:CipherData>
               <xenc:ReferenceList>
                  <xenc:DataReference URI="#EncDataId-6393126" />
               </xenc:ReferenceList>
            </xenc:EncryptedKey>
         </wsse:Security>
      </soapenv:Header>
      <soapenv:Body>
         <xenc:EncryptedData Id="EncDataId-6393126" Type="http://www.w3.org/2001/04/xmlenc#Content">
            <xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc" />
            <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
               <wsse:SecurityTokenReference xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
                  <wsse:Reference URI="#EncKeyId-urn:uuid:7934811BD783B11FAA12585771335152" />
               </wsse:SecurityTokenReference>
            </ds:KeyInfo>
            <xenc:CipherData>
               <xenc:CipherValue>9l9w23N4cauBPWarHAMrPI2bjzR5bfZNsHKAC5NTkMNzitW/3JM74mWh83LuhH5e/RAV3mxaDcYJczmLD0tiVqJJGGuF85p+AocHCnjWz1we0/dYaMoDBSYn2kINp+6//70wwGBxHByT53OP7SpLfkJD9Fnnd4TVBg3iwamszR49oCYv64Z3wYLV/Ie6OMVy+kFbcXf/xFG/MpfmrJdkaEwq+uheOrdT3yYneoNoUbvvhmVHirnEXHryGfKu+36pgIpKDkrvbS9EFZ3WmIo+pQwJ3wq2BzFLWK/ackH0LV8=</xenc:CipherValue>
            </xenc:CipherData>
         </xenc:EncryptedData>
      </soapenv:Body>
   </soapenv:Envelope>0
(ちなみにレスポンス方向は暗号化してないので、プレーンなSOAP メッセージのままなのが TCPMon を見るとわかる。) この見所は、以下のようなところ
  • Header に EncryptedKey、Body に EncryptedData があること
  • EncryptedKey の暗号方式はRSA、EncryptedData の暗号方式はAES
  • EncryptedKey の DataReference から、EncryptedData を参照している事
  • EncryptedData の SecurityTokenReference から、EncryptedKey を参照している事
  • X509IssuerSerial に鍵を作ったときに入力した内容が現れている事
■ 補足 Rampart のsample を見ると、タイムスタンプ、署名、署名+暗号化、暗号化部分を別のMIMEパートに分けて出す方法など、必要な情報がほぼ十分に載っていてお勧め。

Rampart 1.4/暗号化/サービス側

前ポストの StockQuoteサービスとクライアントのペアを修正して、暗号化された SOAP メッセージ をやり取りするようにしてみる。まずはサービスから。 ■ サービスを暗号前提にする
  • コールバッククラスを書く。現実のコールバックは LDAP とかを見に行ったりして本物のパスワードを設定するけど、関係ないのでここは手を抜く。
    package samples.quickstart.callback;
    import 略
    
    public class PWCBHandler implements CallbackHandler {
       public void handle(Callback[] callbacks) 
             throws IOException, UnsupportedCallbackException {
          for (Callback callback: callbacks) {
             WSPasswordCallback pwcb = (WSPasswordCallback)callback;
             System.out.println("ID=" + pwcb.getIdentifer());
             pwcb.setPassword("rampart");
          }
       }
    }
  • resources/META-INF/services.xml に暗号機能を追記する。
    <service name="StockQuoteService" scope="application" targetNamespace="http://quickstart.samples/">
        <descript…
          …略
        …name="ServiceClass">samples.quickstart.service.pojo.StockQuoteService</parameter>
        <module ref="rampart" />
        <parameter name="InflowSecurity">
          <action>
            <items>Encrypt</items>
            <passwordCallbackClass>samples.quickstart.callback.PWCBHandler</passwordCallbackClass>
            <decryptionPropFile>service.properties</decryptionPropFile>
          </action>
        </parameter>
    </service>
  • 上のコードの<decryptionPropFile>から参照されているファイル service.properties を、resources/ 下に作り、arrファイル 直下に含まれるようにbuild.xml を編集。
    org.apache.ws.security.crypto.provider=org.apache.ws.security.components.crypto.Merlin
    org.apache.ws.security.crypto.merlin.keystore.type=jks
    org.apache.ws.security.crypto.merlin.keystore.alias=service
    org.apache.ws.security.crypto.merlin.keystore.password=rampart
    org.apache.ws.security.crypto.merlin.file=ourkeys.jks
  • 上記 service.properties の中から参照されているキーストア ourkeys.jksを、keytool を使って resources 下に作る。キーストア・パスワードを聞いてくるので、service.properties にあわせて rampart と入力。出来てきた ourkeys.jks も arr ファイル直下に含まれるようにbuild.xml を編集
    keytool -keyalg "RSA" -sigalg "MD5withRSA" -genkeypair ourkeys.jks -alias client
    いろいろ聞かれるので適当に答える
    keytool -keyalg "RSA" -sigalg "MD5withRSA" -genkeypair ourkeys.jks -alias service
    いろいろ聞かれるので適当に答える
  • generate.service で arr ファイルに丸める。中身はこんな感じ
    ourkeys.jks
    service.properties
    META-INF/
       MANIFEST.MF
       services.xml
    samples/
       quickstart/
          callback/
             PWCBHandler.class
          service/
             pojo/
                StockQuoteService.class
  • 再デプロイする。手でコピーしても良いけど、一発で成功しなかったら後で繰り返すとき面倒なので ant タスクを書いておくと便利。
    <target name="deploy.service" depends="generate.service">
        <copy file="${build.dir}/StockQuoteService.aar" 
           toDir="${TOMCAT_HOME}/webapps/axis2/WEB-INF/services"/>
    </target>
soapUI なんかを使って、暗号化なしの素の getPrice メッセージを送ってみると、サーバ側で以下のような例外がトレースされる。
org.apache.axis2.AxisFault: WSDoAllReceiver: 
Incoming message does not contain required Security header
「セキュリティヘッダが見当たらない」と言ってきたので、ひとまず上手くいった模様。 次は、クライアントがリクエストを暗号化して送るようにする。→[次ポスト]

2009年11月19日木曜日

Rampart 1.4/quickstart

Axis のサンプルを独立にビルド・デプロイして、独立のクライアントから呼ぶ方法。 こういうシンプルなサービスとクライアントのペアを作れるようにしておくと、例えば、これに対して暗号化を追加してみるとか、いろんな実験の叩き台として便利。 以下、前提
  • Tomcat v6.0 が{tomcat-home} にインストールされている
  • Axis2 1.5 が{axis2_home} にインストールされている
■ quickstart のビルド
  • 適当な所に{axis2_home}/samples/quickstart をコピーする。
  • build.xml の AXIS2_HOME が相対パスで指定されているので、絶対パス(もしくは環境変数)に書き換える。
  • ant を叩いてビルド。generics 関連の警告が出るので嫌だったら直す。
  • 出来上がったStockQuoteService.aarを{tomcat-home}/webapps/axis2/WEB-INF/services下に置く。面倒なので build.xml に deploy.service 的な ターゲットを書いておくと楽。
  • axis2 の管理コンソール を開いてWSDLのURLを得る
  • その WSDL を利用して、soapUI とかからメッセージを投げて動きを確かめる。 update{symbol="hundred", price=100}のような適当なメッセージを送って、getPrice{symbol="hundred"}で return=100 なら成功
■ クライアントコードを書く。
  • WSDL を作成する。ant generate.wsdl
  • WSDL からクライアントスタブを作成する。
    <target name="generate.stub">
      <taskdef name="axis2-wsdl2java"
                classname="org.apache.axis2.tool.ant.AntCodegenTask"
                classpathref="axis2.classpath"/>
      <mkdir dir="generated"/>
      <axis2-wsdl2java
                wsdlfilename="${build.dir}/StockQuoteService.wsdl" output="generated/" />
    </target>
    
  • クライアントのエントリポイントを書く
    package samples.quickstart;
    
    import samples.quickstart.StockQuoteServiceStub.*;
    
    public class Client {
      public static void main(String[] args) throws Exception {
        StockQuoteServiceStub service = new StockQuoteServiceStub();
        GetPrice arg0 = new GetPrice();
        arg0.setSymbol("hundred");
        GetPriceResponse response = service.getPrice(arg0);
        System.out.println("price=" + response.get_return());
      }
    }
  • price=100 がコンソール出力されたら成功
■ 自動生成クライアントスタブを使わない場合
  • こんな感じのコードでも可
    public class Client {
        private static final String URL 
      = "http://localhost:8888/axis2/services/StockQuoteService.StockQuoteServiceHttpSoap12Endpoint";
    
     private static final String AXIS2_REPO = "axis-repo";
    
        public static void main(String[] args) throws Exception {
            ConfigurationContext ctx = 
                ConfigurationContextFactory.createConfigurationContextFromFileSystem(
                    AXIS2_REPO, AXIS2_REPO + "/conf/axis2.xml");
            
            ServiceClient client = new ServiceClient(ctx, null);
            Options options = new Options();
            options.setAction("urn:getPrice");
            options.setTo(new EndpointReference(URL));
            client.setOptions(options);
            
            OMElement response = client.sendReceive(getPayload("hundred"));
            System.out.println(response);
        }
        private static OMElement getPayload(String value) {
            OMFactory factory = OMAbstractFactory.getOMFactory();
            OMNamespace ns = factory.createOMNamespace(
              "http://quickstart.samples/xsd","ns1");
            OMElement elem = factory.createOMElement("getPrice", ns);
            OMElement childElem = factory.createOMElement("symbol", null);
            childElem.setText(value);
            elem.addChild(childElem);
            
            return elem;
        }
    }
  • ちょっと何かを試してみる程度なら、スタブ版よりこっちの方が見通しが良くて楽かもしれない。

2009年11月18日水曜日

Axis2 1.5/Rampart 1.4

Rampart をいろいろ試していると、暗号化するところで下のような例外が発生してなかなか解決しない。何やら署名/暗号化のアルゴリズムがサポートされていないという例外が出る。
org.apache.ws.security.WSSecurityException: 
WSHandler: Encryption:
error during message processingorg.apache.ws.security.WSSecurityException:
An unsupported signature or encryption algorithm was used
(unsupported key transport encryption algorithm:
No such algorithm: http://www.w3.org/2001/04/xmlenc#rsa-1_5)

一応、原因と解決策がわかったので、記録しておく。
参考URL:No such algorithm: http://www.w3.org/2001/04/xmlenc#rsa-1_5

■ 原因
暗号技術は米国の輸出法で規制されていて、JDK (JRE)のデフォルトでは、制限つきの暗号レベルになっている。

■ 対策
以下のステップで解決
◆Java Cryptography Extension (JCE) を 「Unlimited Strength Jurisdiction Policy Files 6 」に差し替える。
  • jce_policy-6.zip をダウンロードして展開する。
  • 新しい local_policy.jar と US_export_policy.jar で、{JDK_HOME}/jre/lib/security 下のものを置き換える。詳しくは README.txt参照。

◆対応するセキュリティプロバイダが、実行時に使われるようにする。
  • bcprov-jdk16-144.jarをダウンロードする。
  • クラスパスに含める。スタンドアロンなら単にjava コマンドの-classpath に含まれるようにする。Web アプリなら、多分 WEB-INF/lib に置けばいいはず。

■ 補足
参考URLと以下の点で違う。
  • security.policyファイル に security.provider.nを指定する箇所があるが、無くても大丈夫だった。
  • jre/lib/ext には、とりあえず追加しなくても問題なかった。

2009年11月17日火曜日

Rampart sample と TCPmon

Axis2 1.5.1で rampart 1.4 の sample を動かして、SOAP メッセージを TCPMon で観察するやり方のメモ。basic と policy があるけど、ここでは basic で説明。Windows 環境で、基本、コマンドプロンプトから Ant 叩くだけ。 (以下、rampart を展開したディレクトリを {rampart}とする。あと {rampart}\samples\basic\README.txt を読んでいること前提。) ■ 準備
  • コマンドプロンプトから、{rampart}/samples/basic に行く
  • 環境変数 AXIS2_HOME が 設定されていることを確認。
  • build.xml の client.port プロパティを 8080 から適当な値に書き換える(ここでは 8888 とした)。このポートがTCPMonの受け口になる。
    <property name="client.port" value="8888"/>
  • addressing.mar プロパティを addressing-1.4.mar から ~1.5.1 に書き換える
    <property name="addressing.mar" value="addressing-1.5.1.mar"/>
  • client.axis2.xml の以下の部分をコメントアウトする
    <!--    <transportSender name="tcp"
                         class="org.apache.axis2.transport.tcp.TCPTransportSender"/>-->
■ 実行 まずは、とりあえず basic 下の sample01 を動かしてみる。
  • TCPMon を起動。Admin タブで以下の設定を追加
    • Listen Port 8888
    • Target Hostname 127.0.0.1
    • Target Port 8080
  • コマンドプロンプトで、samples\basic下からサービスを立ち上げる。自前の HTTPサーバを立てている模様。
    ~~\samples\basic>ant server.01    
  • もう一個のコマンドプロンプトでクライアントを実行する。
    ~~\samples\basic>ant client.01    
  • 以下の出力を確認(ほんとは1行)
    [java] <ns:echoResponse xmlns:ns=
        "http://sample01.samples.rampart.apache.org">
        <ns:return>Hello world</ns:return></ns:echoResponse>
  • ログを見たかったら、{rampart}\samples\basic\build\temp_client に log4j.properties を適当に書けばダラダラ出力される。
  • TCPMon はこんな感じになる。sample01 は WS-Security を使っていないので、Adressing が付いただけのあっさりしたヘッダ。
  • ちなみにsample02をやってみると、SOAPヘッダにこんなのが追加されたのわかる。
     <wsse:Security 
           xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
           soapenv:mustUnderstand="1">
       <wsu:Timestamp 
           xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" 
           wsu:Id="Timestamp-7866553">
         <wsu:Created>2009-11-16T10:47:08.375Z</wsu:Created>
         <wsu:Expires>2009-11-16T10:52:08.375Z</wsu:Expires>
       </wsu:Timestamp>
       <wsse:UsernameToken 
           xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" 
           wsu:Id="UsernameToken-31658378">
         <wsse:Username>bob</wsse:Username>
         <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest"
           >KnUNxZhw8uDNeBpJXOwb6uZzdP8=</wsse:Password>
         <wsse:Nonce>O0u/xram0QS4x7P6sgFijQ==</wsse:Nonce>
         <wsu:Created>2009-11-16T10:47:08.359Z</wsu:Created>
       </wsse:UsernameToken>
     </wsse:Security>
ちょっと面白い。

2009年11月16日月曜日

Tomcat v6.0/Axis2 1.5.1/Rampart 1.4

Tomcat に Axis Rampart を組み込んでみる。

  • rampart 1.4 をダウンロードして展開(以降 {RAMPART_HOME})。
  • Tomcat の webapps に axis2.war (war 版でダウンロードしたもの) を置く。hotdeploy が効いて、展開された war から axis2フォルダ(以下 {AXIS_HOME})が作られるのを確認。
  • {RAMPART_HOME}/module 下の*.jar を、{AXIS_HOME}/WEB-INF/modules 下にコピーする。
  • {RAMPART_HOME}/lib 下の*.jar を、{AXIS_HOME}/WEB-INF/lib 下にコピーする。
  • (上の二つのコピーは、sample 下のbuild.xml を Ant で叩いても良いらしい。)
  • Quick Start Guide には log4j と xalan を別途用意するようなことが書いてるけど、Axis2 1.5.1には最初から入ってるっぽい。)
  • Tomcat を立ち上げて、ブラウザで http://localhost:8080/axis2/ を開く。
  • [Administration] から username=admin, password=axis2でログイン
  • "engage module">[For all Services]>[Select a Module] で rampart を指定し、[Engage]。
  • "System Components">[Available Modules] で、rampart が入った事を確認。

WS-Security の準備ができた。

2009年11月15日日曜日

Axis2 1.4/Galileo/WTP

古い developerworks の記事事の Web サービスのサンプルを、Eclipse 3.5 Galileo の WTP から動かしてみる。

結構良さげな記事なのに、細かいところが各製品の最近の版といろいろ食い違っていてもったいない。それと Eclipse でできるかも試してみたい。というわけでやってみる。(記事を見るには登録が必要)

■ 事前準備
まずは、Axis2 1.5.1 をダウンロードして展開して。Eclipse 3.5 の [Preference] -> [Web Services] から、Axis2 の runtime location として設定しておく。重要:現時点(2009/11/15)最新の Axis2 1.5.1 だと、何かがかみ合わなくて動かないので、1.4.1 を使う。)

■ Dynamic Web Project 作成
  • 名前はとりあえず"axis2-trial1"とする
  • Configuration で Axis2 を選択。無かったら [Modify...]で Axis2 Web Services にチェックして Project Facet に追加指定。
  • Finish する。WEB-INF の下がちょっと独特なので、観察しておく。
■ Web サービス作成
  • リスト 26 の CMSService クラスを作る。
  • リスト 22 の services.xml を書く。
  • CMSService 上のコンテキストメニューから Web サービスを新規作成。
    • CMSServiceクラスからの Bottom up になっている事を確認
    • スライダーを Assemble service に下げる
    • [Next]で、service.xml を指定する。
    • Finish
  • WEB-INF/service 下に自動生成ファイルが出来てくるので、ちょっと観察しておく。
■ 実行してみる
  • Servers ビューで、Tomcat v6.0 に プロジェクトを追加
  • Tomcat v6.0 を起動
  • ブラウザから下記URLを開いて管理コンソールが立ち上がっているのを確認。
    http://localhost:8080/axis2-trial1/axis2-web/index.jsp
  • 管理コンソール の [Services]→[CMSService] で wsdl を確認
  • 上記 WSDL を用いて、ブラウザとか、Eclipse の Web Service Explorer とか、soapUI (フリーのSOAPツール。お勧め。)とかで確認。
    • getNumberOfArticle では、固定値42が返される。
    • addArticle では、Eclipse のコンソールにリクエストがトレースされる。ただしレスポンスは返されない(非同期)
  • リスト27リスト30のクライアントコードを試してみる。ポート番号を書き換えれば、WTP のTCP/IP Monitor で SOAP メッセージが確認できる。

2009年11月14日土曜日

WS-Addressing/CXF 2.2.4

前回のポストで、ある文字列を渡すと、それを加工して別の文字列を返すWeb サービスを作った。これに WS-Addressing を追加してみる。 (作業環境は前回同様 Galileo + Tomcat v6.0。CXF 2.2.4 は Web service runtime として使用。) もともとのSOAPメッセージは、こんなリクエストとレスポンスだった。 ◆リクエスト
<soap:Envelope 
    xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <ns2:greet xmlns:ns2="http://mycompany.com/greeting/">
      <username>World</username>
    </ns2:greet>
  </soap:Body>
</soap:Envelope>
◆レスポンス
<soap:Envelope 
    xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <ns2:greeting-response xmlns:ns2="http://mycompany.com/greeting/">
      <return>Hello, World!</return>
    </ns2:greeting-response>
  </soap:Body>
</soap:Envelope>
これが、WS-Addressing を使うと、ヘッダのところがこんな風になる。 ◆リクエスト
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Header>
    <Action xmlns="http://www.w3.org/2005/08/addressing">
      http://mycompany.com/greeting/Greeting/greet
    </Action>
    <MessageID xmlns="http://www.w3.org/2005/08/addressing">
      urn:uuid:364e3949-e49a-44e7-a6c9-843ac2acce3c
    </MessageID>
    <To xmlns="http://www.w3.org/2005/08/addressing">
      http://localhost:8080/cxf-trial1/services/GreetingPort
    </To>
    <ReplyTo xmlns="http://www.w3.org/2005/08/addressing">
      <Address>
        http://www.w3.org/2005/08/addressing/anonymous
      </Address>
    </ReplyTo>
  </soap:Header>
  <soap:Body>
  ・・・
◆レスポンス
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Header>
    <Action xmlns="http://www.w3.org/2005/08/addressing">
      http://mycompany.com/greeting/Greeting/response
    </Action>
    <MessageID xmlns="http://www.w3.org/2005/08/addressing">
      urn:uuid:764d8c64-d8fb-46ba-8bee-d86761386b8d
    </MessageID>
    <To xmlns="http://www.w3.org/2005/08/addressing">
      http://www.w3.org/2005/08/addressing/anonymous
    </To>
    <RelatesTo xmlns="http://www.w3.org/2005/08/addressing">
      urn:uuid:364e3949-e49a-44e7-a6c9-843ac2acce3c
    </RelatesTo>
  </soap:Header>
  <soap:Body>
  ・・・
リクエストに返信先を示す<ReplyTo>が含まれ、レスポンスにはもともとのメッセージを識別するIDが含まれているのがわかる。 このための作業は、CXF 的には Feature と言うものを指定して行うらしい。例えば今回は、サービス側のエンドポイントを beans.xml で構成したけど、その jaxws:endpoint で以下のように Feature を追加する。
    <jaxws:features>
      ・・・
        <wsa:addressing xmlns:wsa="http://cxf.apache.org/ws/addressing"/>
    </jaxws:features>
またクライアント側は 非 Spring の Java コードで書いたけど、その場合こんな風にFeature を追加する。
    factory.getFeatures().add(new WSAddressingFeature());
Eclipse の Type Hierarchy で見ると、WSAddressingFeature の他に、LoggingFeature や WS-Policy などの AbstractFeature の派生クラスが12個ほどあるらしい。 今回は WS-Addressing の細部にまで深入りできないけど、何となく CXF の作法がわかってきた。beans.xml で Feature を付与するようにして、サービス実装クラス(ビジネスロジック)への変更を拝するというのは、これも separation of concern の良い例だなあと思う。 他にも WS-* 関連の CXF 実装を調べて見たいが、思ったより資料が少なくて難しい。

Apache CXF 2.2.4/Tomcat v6.0/Eclipse 3.5

Apache CXF を、Eclipse の WTP から Tomcat v6.0 で動かしてみる。 ■ Eclipse の準備
  • SOA Tools Platform プラグインをインストールしておく。update site は "http://download.eclipse.org/releases/galileo"。
  • Servers ビューで、Tomcat v6.0 サーバを構成しておく
  • [Preference]から[Web Services]→[CXF 2.0 Preferences]の[CXF Runtime]タブで、CXF Home に CXF のインストールディレクトリを指定。
  • 続けて、[Server and Runtime] で Tomcat v6.0 とApache CXF 2.xを指定
■ プロジェクトと WSDL ファイルの作成
  • Dynamic Web Project を作成する。(ここでは プロジェクト名:cxf-trial1とした)
  • Project Facets で CXF 2.x Web Services が指定されていることを確認
  • 新規ソースフォルダ resources を作成する。
  • resources 下に greeting.wsdl を作成する。
    <?xml version="1.0" encoding="UTF-8"?>
    <wsdl:definitions name="greeting"
       targetNamespace="http://mycompany.com/greeting/"
       xmlns:tns="http://mycompany.com/greeting/"
       xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
       xmlns:xsd="http://www.w3.org/2001/XMLSchema"
       xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/">
       <wsdl:types>
          <xsd:schema targetNamespace="http://mycompany.com/greeting/">
             <xsd:element name="greet">
                <xsd:complexType>
                   <xsd:sequence>
                      <xsd:element name="username" type="xsd:string" />
                   </xsd:sequence>
                </xsd:complexType>
             </xsd:element>
             <xsd:element name="greeting-response">
                <xsd:complexType>
                   <xsd:sequence>
                      <xsd:element name="return" type="xsd:string" />
                   </xsd:sequence>
                </xsd:complexType>
             </xsd:element>
          </xsd:schema>
       </wsdl:types>
       <wsdl:message name="request">
          <wsdl:part name="parameters" element="tns:greet" />
       </wsdl:message>
       <wsdl:message name="response">
          <wsdl:part name="parameters" element="tns:greeting-response" />
       </wsdl:message>
       <wsdl:portType name="Greeting">
          <wsdl:operation name="greet">
             <wsdl:input message="tns:request"/>
             <wsdl:output message="tns:response"/>
          </wsdl:operation>
       </wsdl:portType>
       <wsdl:binding name="GreetingSoapBinding" type="tns:Greeting">
          <soap:binding style="document"
             transport="http://schemas.xmlsoap.org/soap/http" />
          <wsdl:operation name="greet">
             <wsdl:input>
                <soap:body use="literal" />
             </wsdl:input>
             <wsdl:output>
                <soap:body use="literal" />
             </wsdl:output>
          </wsdl:operation>
       </wsdl:binding>
       <wsdl:service name="GreetingService">
          <wsdl:port name="GreetingPort" binding="tns:GreetingSoapBinding">
             <soap:address location="http://localhost:8080/GreetingService" />
          </wsdl:port>
       </wsdl:service>
    </wsdl:definitions>
■ Web サービスのスケルトンの作成
  • greeting.wsdl のコンテキストメニューから新規 Web Service を作成(Web Service Runtime に、CXF 2.x が設定されている事を確認)
  • http://localhost:8080/cxf-trial/services/ で Greeting の WSDL へのリンクが表示され、これを辿ると WSDL が表示される事を確認。
  • Web Service Explorer から greet を実行してみると、与えた文字列に関係なく、自動生成コードにより固定文字列(_return-1938145346みたいな)が返される事を確認。
■ Web サービスのスケルトンの実装 GreetingImpl.greet()を書き換える
   public java.lang.String greet(java.lang.String username) { 
      return String.format("Hello, %s!", username);
   }
WTP なので、変更したら勝手に 再発行される。Web Service Explorer で 文字列 "World" を与えて greet を実行してみると、"Hello, World!" が返される事を確認。 これで、他の CXF の機能(WS-Security とか)を試す準備ができた。