特集 
次世代XML Webサービスを試す Part 2
2.セキュリティ・トークンと認証 
インフォテリア株式会社 
              吉松 史彰 
2002/11/15  | 
  | 
セキュリティ・トークンと認証
 前述のUsernameToken要素で表されるセキュリティ・トークンによって表明された申告は、次の2点である。
- 送信元はuser1である
 
- 送信元はかくかくしかじかの秘密を受信先と共有している
 
 このように、メッセージの送信元の身元(アイデンティティ)に関する申告の表明が、本当に正しい申告であるかどうかを確認するプロセスのことを「認証」と呼ぶ。認証によって、送信元が誰なのか(または誰でないのか)が確認できる。
 WS-Security仕様には、申告の表明を具体的にどのように処理するかについては記述されていない。そのため、MicrosoftのWSDKとIBMのWSTK(Web Services Toolkit)では異なる方法で認証処理を行っているのは前回解説したとおりだ。ここではMicrosoftのWSDKの仕組みをもう一度おさらいしよう。
 前回紹介したWSDKを用いたWebサービス・クライアントでは、次のようなコードでUsernameTokenをSOAPヘッダに添付していた。
localhost.Service1 svc = new localhost.Service1(); 
 
Microsoft.WSDK.SoapContext ctx = svc.RequestSoapContext; 
Microsoft.WSDK.Security.UsernameToken user = 
  new Microsoft.WSDK.Security.UsernameToken("user1", "pass1", 
    Microsoft.WSDK.Security.PasswordOption.SendPlainText); 
ctx.Security.Tokens.Add(user); 
 | 
 
  | 
 
|  Webサービス呼び出し時に、SOAPヘッダにUsernameTokenを添付するためのコード | 
 Microsoft.WSDK.Security.UsernameTokenクラスのコンストラクタの第3引数に「PasswordOption.SendPlainText」を指定している。このため、前回のサンプルでは次のようなUsernameTokenが送られていた。これは、WS-Security仕様で定義されている、パスワードをクリア・テキストで送信する手順で作成されたセキュリティ・トークンだ。
<wsse:UsernameToken 
    xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility" 
    wsu:Id="SecurityToken-85d37100-84fd-4378-99b1-8fe368508755"> 
  <wsse:Username>user1</wsse:Username> 
  <wsse:Password Type="wsse:PasswordText">pass1</wsse:Password> 
  <wsse:Nonce>kWzmUXFIHxnXQ+p7+NqsbA==</wsse:Nonce> 
  <wsu:Created>2002-09-14T05:31:26Z</wsu:Created> 
</wsse:UsernameToken> 
 | 
 
  | 
 
|  UsernameTokenクラスで「SendPlainText」を指定した場合に作成される、パスワードがクリア・テキストなセキュリティ・トークンの例 | 
 WSDKでは、上記以外の2つの方法でもUsernameTokenを作成できる。まず、PasswordOption.SendNoneを指定すると、次のようなUsernameTokenが作成される。これは、上記で解説した、パスワードが指定されていないトークンだ。
<wsse:UsernameToken 
    xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility" 
    wsu:Id="SecurityToken-a36a8615-0f86-498e-be5e-c4b15dd65f01"> 
  <wsse:Username>user1</wsse:Username> 
  <wsse:Nonce>NgJEugg7OzpgLSmRiA6Ymw==</wsse:Nonce> 
  <wsu:Created>2002-10-21T02:38:17Z</wsu:Created> 
</wsse:UsernameToken> 
 | 
 
  | 
 
|  UsernameTokenクラスで「SendNone」を指定した場合に作成される、パスワードが指定されていないセキュリティ・トークンの例 | 
 また、PasswordOption.SendHashedを指定すると、補遺で定義されたノンスとタイムスタンプを含んだパスワードのハッシュが作成される。
<wsse:UsernameToken 
    xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility" 
    wsu:Id="SecurityToken-139ac9b2-493d-4b3f-9fab-bb17038c86fe"> 
  <wsse:Username> 
    user1 
  </wsse:Username> 
  <wsse:Password Type="wsse:PasswordDigest"> 
    tOVVSsx51jL/iXvzPnJ2gYSYvg4= 
  </wsse:Password> 
  <wsse:Nonce> 
    v0yDmxGuLEdnqHY/2IXAow== 
  </wsse:Nonce> 
  <wsu:Created> 
    2002-10-21T02:24:39Z 
  </wsu:Created> 
</wsse:UsernameToken> 
 | 
 
  | 
 
|  UsernameTokenクラスで「SendHashed」を指定した場合に作成される、ノンスとタイムスタンプを含んだパスワードのハッシュを持つセキュリティ・トークンの例 | 
 上述したとおり、SOAPメッセージの伝送路がエンド・ツー・エンドで信頼できる場合を除いて、パスワードは最低でもPasswordOption.SendHashedを指定して送信しなければならない。
 UsernameToken要素で送られたセキュリティ・トークンをサーバ側で処理するために、WSDKはIPasswordProviderインターフェイスを利用する。XML Webサービスに送られてきたSOAPメッセージは、Web.configに追加されている次の要素によって、まずMicrosoft.WSDK.HttpModuleクラスによって処理される。この処理は、asmxファイル(とそのコードビハインド)による処理よりも前に行われる。
<httpModules> 
  <add name="WSDK" type="Microsoft.WSDK.HttpModule, Microsoft.WSDK,Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> 
</httpModules> 
 | 
 
  | 
 
|  Webサービスに送られてきたSOAPメッセージを、Microsoft.WSDK.HttpModuleクラスで処理するように指示したWeb.configの要素 | 
 HttpModuleクラスは、SOAPヘッダのSecurity要素にUsernameToken要素を発見すると、IPasswordProviderインターフェイスを実装しているクラスのオブジェクトを作成して、そのGetPasswordメソッドを呼び出す。その際、引数としてUsernameToken要素のUsername要素の内容(つまりユーザー名)を渡す。IPasswordProviderインターフェイスを実装しているクラスは、Web.configの次の記述を参照して決められる。以下の場合では、「WSDK1」という名前のアセンブリに含まれるWSDK1.PasswordProviderクラスのオブジェクトが作成されるわけだ。
<microsoft.wsdk> 
  <security> 
    <passwordProvider type="WSDK1.PasswordProvider, WSDK1" /> 
  </security> 
</microsoft.wsdk> 
 | 
 
  | 
 
|  IPasswordProviderインターフェイスを実装したクラスを指定するためのWeb.configの記述例 | 
 このクラスは、IPasswordProviderインターフェイスのGetPasswordメソッドを実装する。このメソッドで実装すべき内容に関するWSDKのドキュメントの記述は間違っているので注意してほしい。このメソッドの正しい内容は、とにかくどんな方法でもいいので、引数として渡されたユーザー名に一致する正しいパスワードを取り出して、文字列で返すコードでなければならない。例えば、ユーザー名をキーにしてデータベースにアクセスし、パスワードを取得してもいいだろう。WSDKの現在の実装では、サーバ側もパスワードのクリア・テキストを知っている必要がある。パスワードが前回のサンプルのように平文で送られていても、今回解説したようなダイジェストで送られていても、GetPasswordメソッドは平文のパスワードを返さなければならない。GetPasswordメソッドから返されたパスワードが、送られてきたパスワード(を平文にしたもの)と一致すれば、WSDKにおける認証はOKと見なされる。一致していなかった場合は、認証エラーを示すSOAP Faultが自動的に返され、asmxファイルのWebMethodは実行されない。なお、UsernameTokenにパスワードが指定されていない(PasswordOption.SendNone)場合は、GetPasswordメソッドは呼び出されるが、実はパスワードとして何を返しても、処理はそのまま素通りしてしまうことにも注意してほしい。
 ただし、SSLなどを使っていないために伝送路が信頼できない場合は、単純に平文でパスワードが一致しただけで認証をOKと見なしてはならない。メッセージを事前にのぞき見した悪意のクライアントが送信したものかもしれないからだ。従って上述したとおり、UsernameTokenにパスワードを含める場合は、クライアントは必ずWS-Securityの補遺に解説されているノンスとタイムスタンプとを組み合わせたダイジェストを送信してくる。そこでそれを受け取ったサーバ側では、単純な平文ではなくダイジェストが送られてきたことも併せて確認しなければならない*1。
  
 
    
| *1 さらに、リプレイ攻撃を防ぐために、ノンスとタイムスタンプに同じ値が繰り返し使われていないこともあわせて確認する必要がある。 | 
 次のコードで、パスワードが送られてこなかった場合(PasswordOption.SendNone)とパスワードが平文で送られてきた場合(PasswordOption.SendPlainText)の両方に対処できる。なお、このコードはGetPasswordメソッドの内部には書けない。WSDKではサーバ側でこのようなコードを実行しなければならない。GetPasswordメソッドやHttpModuleクラスはこれを自動的には行わないので注意が必要だ(このあたりは恐らく今後のWSDKのリリースとともに改善されていくだろう)。
 
private bool CheckToken() { 
  Microsoft.WSDK.SoapContext ctx 
    = Microsoft.WSDK.HttpSoapContext.RequestContext; 
  Microsoft.WSDK.Security.UsernameToken user = null; 
  foreach(Microsoft.WSDK.Security.SecurityToken token 
                                in ctx.Security.Tokens) { 
    user = token as Microsoft.WSDK.Security.UsernameToken; 
    if (user != null) 
      break; 
  } 
  if (user == null) 
    return false; 
  return (user.PasswordOption 
    == Microsoft.WSDK.Security.PasswordOption.SendHashed); 
} 
 
[WebMethod] 
public string GetData() { 
  if (!CheckToken()) 
    throw new Microsoft.WSDK.SoapFault( 
      "Something bad happened because of you.", 
      new System.Xml.XmlQualifiedName( 
	    "Fault.Nevertheless", "urn:IAmTheLaw")); 
 
  ……正常処理…… 
} 
 | 
 
  | 
 
|  WSDKを用いたWebサービスで必要となる、パスワードがノンスとタイムスタンプとを組み合わせたダイジェストかどうかを確認するコード例 | 
 受け取ったSOAPメッセージにセキュリティ・トークンが付いていた場合、WSDKのHttpModuleがそれをSoapContextクラスのSecurityプロパティのTokensプロパティに格納する。そこで、上記のコードではTokensプロパティをループしながら1つ1つのトークンを調べて、UsernameTokenを取り出している。取り出したUsernameTokenのオプションをチェックすれば、どのようなトークンが送られたのかが分かる。