次の方法で共有



August 2010

Volume 25 Number 08

フェデレーション ID - WIF を使用した ASP.NET のパッシブ認証

Michele Leroux Leroux | August 2010

フェデレーション セキュリティの目標は、ドメイン間で信頼関係を確立するメカニズムを提供することです。その結果、ユーザーは、自身が参加するドメインで認証されるだけでなく、信頼関係のある別のドメインに属するアプリケーションやサービスへのアクセスも許可されるようになります。これにより、シングル サインオンのような認証手法が可能になり、ユーザーは、複数のアプリケーションやサービスにアクセスするために複数のアカウントを準備して管理する必要がなくなるため、信頼できる利用者にアプリケーションを拡張するコストが大幅に削減されます。

フェデレーション セキュリティ モデルでは、ID プロバイダー (IdP) が認証を行い、セキュリティ トークン サービス (STS) を提供して、セキュリティ トークンを発行します。次に、発行されたトークンによって、認証済みユーザーに関する情報、つまりユーザーの ID や、場合によってはその他の情報 (ロール、よりきめ細かいアクセス権など) がアサートされます。フェデレーション環境ではこのようにアサートされる情報をクレームと呼び、このクレームベースのアクセス制御がフェデレーション セキュリティ モデルの中心的役割を果たします。このモデルでのアプリケーションやサービスは、信頼された発行者 (STS) からのクレームに基づいてさまざまな機能へのアクセスを承認します。

Windows Identity Foundation (WIF) のようなプラットフォーム ツールにより、この種の ID フェデレーションのサポートが大幅に容易になります。WIF は、クレームベースのアプリケーションやサービスの構築と、SOAP ベース (アクティブ) およびブラウザーベース (パッシブ) のフェデレーション シナリオのサポートを目的とする ID モデル フレームワークです。2009年 11 月号の MSDN Magazine の記事「WIF によるクレームベースの承認」では、WIF と Windows Communication Foundation (WCF) の併用に重点を置いて説明し、WCF サービス向けのクレームベースのセキュリティ モデルの実装方法や、ID フェデレーションへの移行方法についても説明しました。

今回の記事はこの続編として、パッシブ フェデレーションに注目します。今回は、パッシブ フェデレーションのコミュニケーションの流れについて説明し、ASP.NET アプリケーションでフェデレーションを実現するいくつかの手法を紹介します。さらに、ASP.NET のクレームベースの承認手法を解説し、シングル サインオンとシングル サインアウトのシナリオについても説明します。こうした説明の中で、パッシブ フェデレーションのシナリオをサポートする、基盤となる WIF の機能やコンポーネントを紹介します。

パッシブ フェデレーションの基礎

パッシブ フェデレーションのシナリオは、WS-Federation 仕様に基づきます。この仕様には、信頼関係の確立を容易にする、セキュリティ トークンの要求方法や、フェデレーション メタデータ ドキュメントを公開および取得する方法が記述されています。WS-Federation には、シングル サインオンとシングル サインアウトの手順など、フェデレーション実装の概念も記述されています。

WS-Federation にはフェデレーションに関する多くの情報が詳しく記載されていますが、この中には HTTP GET と POST、ブラウザーのリダイレクト、および Cookie を使用してフェデレーション セキュリティの目標を達成するための、ブラウザーベース フェデレーションを中心とするセクションがあります。

パッシブ フェデレーションのメッセージングには、WS-Trust 仕様に密接に基づいている側面がいくつかあります。たとえば、パッシブ フェデレーションでは、STS に対してセキュリティ トークンを要求するときに、RST (Request for Security Token) と RSTR (RST Response) について、ブラウザー互換の形式を使用します。今回のパッシブ フェデレーション シナリオでは、RST をサインイン要求メッセージ、RSTR をサインイン応答メッセージと呼びます。WS-Trust 仕様は、Windows クライアントと WCF サービスの間のフェデレーションなど、SOAP ベース (アクティブ) フェデレーションに重点を置いています。

図 1 に、シンプルなパッシブ フェデレーションのシナリオを示します。

シンプルなパッシブ フェデレーションのシナリオ

図 1 シンプルなパッシブ フェデレーションのシナリオ

ユーザーは、自身が参加するドメインに認証され、各自のロールに応じて Web アプリケーションへのアクセスが許可されます。この認証方式に関与するのは、ユーザー (認証対象者)、Web ブラウザー (要求者)、ASP.NET アプリケーション (証明書利用者、略称 RP)、ドメイン内でユーザーを認証する IdP、およびユーザーのドメインに所属する STS (IP-STS) です。複数のブラウザー リダイレクトのシーケンスにより、必ず、ユーザーが自身のドメインで認証されてから RP にアクセスするようになります。

ユーザーが RP アプリケーションをブラウズ (1) すると、そのユーザーは認証のために自身のドメインの IdP にリダイレクト (2) されます。そのユーザーが IdP でまだ認証されていなければ、IP-STS は資格情報を得るために、チャレンジを提示するか、そのユーザーをログイン ページにリダイレクトします (3)。ユーザーが自身の資格情報を入力 (4) すると、IP-STS によって認証されます (5)。この時点で、IP-STS はサインイン要求に応じたセキュリティ トークンを発行し、そのトークンを含むサインイン応答がブラウザーのリダイレクトによって RP に送信されます (6)。RP は受け取ったセキュリティ トークンを処理し、トークンに含まれるクレームに基づいてアクセスを承認します (7)。承認に成功すると、ユーザーが最初に要求したページが表示され、セッション Cookie が返されます (8)。

WIF と ASP.NET を使ってこのパッシブ フェデレーションのシナリオを実装するには、次の数手順を実行するだけです。

  1. RP と IdP (IP-STS) の間に信頼関係を確立します。
  2. ASP.NET アプリケーション向けのパッシブ フェデレーションを実現します。
  3. 承認チェックを実装して、アプリケーションの機能へのアクセスを制御します。

ここからは、WIF でパッシブ フェデレーションをサポートする機能と、このシンプルなシナリオを実装する手順を順を追って説明し、このシナリオや他のシナリオに関する実際的な考慮事項を紹介します。

WIF のパッシブ フェデレーション向け機能

シナリオの実装について説明する前に、ASP.NET アプリケーション内での ID フェデレーションに特に役立つ WIF の機能を紹介します。まず、WIF には次のような便利な HTTP モジュールが用意されています。

  • WSFederationAuthenticationModule (FAM): ブラウザーベース フェデレーションを実現し、認証とトークン発行のための適切な STS へのリダイレクトを処理します。また、返されたサインイン応答を処理し、発行されたセキュリティ トークンから、承認に使用する ClaimsPrincipal を作成します。また、サインアウト要求など、他の重要なフェデレーション メッセージも処理します。
  • SessionAuthenticationModule (SAM): 認証済みセッションを管理します。つまり、ClaimsPrincipal を含むセッション セキュリティ トークンを生成して Cookie に書き込み、セッション Cookie の有効期間を管理して、このセッション Cookie が提示されたときに Cookie から ClaimsPrincipal を再作成します。また、ローカル セッション トークン キャッシュも管理します。
  • ClaimsAuthorizationModule: カスタム ClaimsAuthorizationManager をインストールする拡張ポイントを提供します。これは、一元的なアクセス確認に便利です。
  • ClaimsPrincipalHttpModule: 要求スレッドにアタッチされた現在のユーザー ID に基づいて ClaimsPrincipal を作成します。また、カスタム ClaimsAuthenticationManager をインストールする拡張ポイントを提供します。これは、要求スレッドにアタッチされる ClaimsPrincipal のカスタマイズに便利です。

ClaimsPrincipalHttpModule は、パッシブ フェデレーションを使用しないアプリケーションに最適です。このモジュールは、パッシブ フェデレーションに移行する前に ASP.NET アプリケーションでクレームベース セキュリティ モデルを実装するための、便利なツールと考えることができます。WCF の場合のこの手法については、前回の記事で説明しました。

それ以外の 3 つのモジュールは、通常、パッシブ フェデレーションで同時に使用しますが、ClaimsAuthorizationModule は使用しなくてもかまいません。図 2 に、これらの主要モジュールが要求パイプラインに統合されるようすと、通常のフェデレーション認証要求でのモジュールの機能を示します。

パッシブ フェデレーションに関与する WIF コンポーネントと HTTP モジュール

図 2 パッシブ フェデレーションに関与する WIF コンポーネントと HTTP モジュール

図 1 に示したパッシブ フェデレーションの流れを念頭に置くと、ユーザーが RP 内の保護されたページを初めてブラウズする (1) ときは、アプリケーションへのアクセスが拒否されます。FAM は未承認の要求を処理し、サインイン メッセージを生成して、ユーザーを IP-STS にリダイレクトします (2)。IP-STS はユーザーを認証し (3)、発行済みのセキュリティ トークンを含むサインイン応答を生成して、その応答を RP アプリケーションにリダイレクトして戻します (4)。

FAM はそのサインイン応答を処理 (認証済みユーザーの有効なセキュリティ トークンが応答に含まれていることを確認) し、サインイン応答から ClaimsPrincipal を作成します (5)。この結果、要求スレッドと HttpContext のセキュリティ プリンシパルが設定されます。続いて、FAM は SAM を使用して ClaimsPrincipal を HTTP Cookie にシリアル化します (6)。ブラウザー セッションの間、この Cookie が以降の要求に提示されることになります。ClaimsAuthorizationModule をインストールしていれば、構成済みの ClaimsAuthorizationManager が呼び出されるため、要求されたリソースにアクセスする前に ClaimsPrincipal に対してグローバルなアクセス確認を実行できます (7)。

要求されたリソースが提示されたら、従来の ASP.NET のログイン用コントロール、IsInRole チェック、およびユーザーのクレームをクエリするその他のカスタム コードを使用して、アクセス制御を実装できます (8)。

以降の要求では、前の要求で SAM が書き込んだ Cookie と共にセッション トークンが提示されます (9)。この要求では、SAM がセッション トークンの検証に関与し、トークンから ClaimsPrincipal を再作成します (10)。要求がサインイン応答でもサインアウト要求でもない場合、またはアクセスが拒否された場合、FAM は使用されません。このような状況は、セッション トークンが提示されないか、期限切れになっっている場合に発生することがあります。

これらのモジュール以外にも、次の 2 つの ASP.NET コントロールがパッシブ フェデレーションで役立ちます。

  • FederatedPassiveSignIn コントロール: 認証が必要な場合にのみ、アプリケーションからすべての未承認呼び出しを、このコントロールをホストするログイン ページにリダイレクトすれば、FAM を代用できます。この手法は、ユーザーがサインインのプロセスと対話することが前提です。このような手法は、アプリケーションでの必要性に応じて、ユーザーが資格情報や、場合によっては最初のログインに追加する資格情報の入力を求められる、ステップアップ認証のシナリオで便利です。FederatedPassiveSignIn コントロールは STS へのリダイレクトを処理し、サインイン応答を処理して、応答の ClaimsPrincipal を初期化し、FAM と SAM で公開されている機能を利用してセキュリティが確保されたセッションを確立します。
  • FederatedPassiveSignInStatus コントロール: このコントロールは、フェデレーション サインアウトのサポートなど、ユーザーが RP アプリケーションにサインインしたり、RP アプリケーションからサインアウトしたりするための対話的手段を提供します。

図 3 に、FederatedPassiveSignIn コントロールを使用した場合にコミュニケーションの流れがどのように変化するかを示します。アプリケーションは、リソースを保護してログイン ページにリダイレクトするためにフォーム認証を使用します (1)。このログイン ページがコントロールをホストします。ユーザーが FederatedPassiveSignIn コントロールをクリックすると (自動的にリダイレクトされるようにもできます)、STS へのリダイレクトがトリガーされます (2)。コントロールをホストするページは STS からの応答を受信し、FAM と SAM を使用してサインイン応答を処理します (3)。続いて、ClaimsPrincipal を作成してセッション Cookie を書き込みます (4)。最初に要求したページにユーザーがリダイレクトされる (5) ときに、SAM がセッション Cookie の検証に関与し、要求の ClaimsPrincipal を作成します。この時点で、ClaimsAuthorizationModule とこのページで承認チェックを実行できます (図 2 参照)。

FederatedPassiveSignIn コントロールを使用したパッシブ フェデレーション

図 3 FederatedPassiveSignIn コントロールを使用したパッシブ フェデレーション

FAM と SAM はどちらも、適切な SecurityTokenHandler 型を使用して受信トークンを処理します。サインイン応答を受信すると、FAM は SecurityTokenHandlerCollection を反復処理して、XML トークンの読み取りに適したトークン ハンドラーを検索します。フェデレーション シナリオでは、通常は Saml11SecurityTokenHandler または Saml2SecurityTokenHandler が適しています。ただし、カスタム トークン ハンドラーを追加すれば、他のトークン形式を使用することもできます。SAM の場合は、SessionSecurityTokenHandler を使用して、セッション Cookie に関連付けられたセッション トークンを処理します。

ID モデルの構成設定には、パッシブ フェデレーションの流れで重要な設定がいくつかあります。このような設定は、FAM と SAM、および FederatedPassiveSignIn コントロールの初期化に使用します (ただし、FederatedPassiveSignIn コントロールでは、Visual Studio デザイナーで構成できるプロパティも公開されています)。プログラムでは、Microsoft.IdentityModel.Configuration 名前空間の ServiceConfiguration 型のインスタンスを指定することも、<microsoft.identityModel> セクションに宣言型の構成を指定することもできます。図 4 に、ID モデルの設定の概要を示します。ここに示した設定の多くについては、この後説明します。

図 4 <microsoft.identityModel> の重要な要素の概要

セクション 説明
<issuerNameRegistry> 信頼された証明書発行者のリストを指定します。信頼されない証明書で署名されたトークンが拒否されるよう、トークンの署名を検証する場合にこのリストを使用します。
<audienceUris> 受信 SAML トークンの有効な対象 URI のリストを指定します。この属性を無効にして、すべての URI を許可することができます (非推奨)。
<securityTokenHandlers> トークン ハンドラーの構成設定をカスタマイズするか、カスタム トークン ハンドラーを用意して、トークンの検証、認証、およびシリアル化の方法を制御します。
<maximumClockSkew> トークンの正当性を判断する際のトークンとアプリケーション サーバーとの間の許容時間差を調整します。既定の時間差は 5 分です。
<certificateValidation> 証明書の検証方法を制御します。
<serviceCertificate> 受信トークンの暗号を解除するサービス証明書を指定します。
<claimsAuthenticationManager> 要求スレッドにアタッチされる IClaimsPrincipal 型をカスタマイズまたは置換するためのカスタム ClaimsAuthenticationManager 型を指定します。
<claimsAuthorizationManager> 中央のコンポーネントから機能へのアクセスを制御するカスタム ClaimsAuthorizationManager 型を指定します。
<federatedAuthentication> パッシブ フェデレーション固有の設定を指定します。

パッシブ フェデレーションを実現する

WIF を使用すると、ASP.NET アプリケーション向けにパッシブ フェデレーションを簡単に構成できます。STS は (WS-Federation 仕様に記載されているとおり) フェデレーション メタデータを提供し、WIF はフェデレーション ユーティリティ (FedUtil.exe) を提供します。このユーティリティは、フェデレーション メタデータを使用して RP と STS の間に信頼関係を確立します (アクティブ フェデレーション シナリオとパッシブ フェデレーション シナリオの両方で役立つ機能の中でも特に役に立ちます)。FedUtil は、コマンド ラインから起動するか、Visual Studio で RP プロジェクトを右クリックして [STS 参照の追加] をクリックすると起動できます。

FedUtil のウィザードでは、次のような簡単な手順で完了できます。

  • ウィザードの 1 ページ目では、ウィザードで変更する構成ファイルと、RP アプリケーションの URI を確認します。
  • 2 ページ目では、RP が信頼関係を確立する対象となる STS のフェデレーション メタデータ XML ドキュメントを参照するパスを入力します。
  • 3 ページ目では、トークンの暗号化解除に使用する証明書を指定します。
  • 最後のページには、STS から提供されるクレームの一覧が表示されます。この一覧を使用すると、たとえば、アクセス制御の決定を計画できます。

ウィザードの手順を完了すると、FedUtil によってプロジェクトが変更され、Microsoft.IdentityModel アセンブリへの参照が追加されます。また、web.config が変更されることで、FAM モジュールと SAM モジュールがインストールされ、これらのモジュール向けの ID モデルの構成設定が指定されます。この結果、アプリケーションでパッシブ フェデレーションがサポートされ、未承認の要求が信頼された STS にリダイレクトされるようになります。

この方法では、RP に関する情報を STS が事前に把握していること、したがって RP にアクセスを試みる認証済みユーザーのトークンを STS が発行すること、および、もちろん、トークンの暗号化解除のために STS で使用するよう RP が要求する公開キーを保持していることを前提としています。これは、簡単に ASP.NET アプリケーションをフェデレーション向けに初期設定できる方法です。当然ながら、この方法を採用すると、調整が必要な場合に最初からアプリケーションを設定する方法や、ウィザードで有効になる基本設定よりも高度な設定方法を理解しやすくなります。ここからは、アプリケーションを "最初から設定する" 方法に重点を置いて説明します。

FedUtil を使用しない場合は、Microsoft.IdentityModel アセンブリへの参照を手動で追加し、必要な ID モデル設定に応じて FAM モジュールと SAM モジュールを手作業で構成する必要があります。HTTP モジュールを追加するセクションは 2 つあり、インターネット インフォメーション サービス (IIS) 6 の場合は system.web、IIS 7 の場合は system.webServer です。アプリケーションが IIS 7 でホストされていると仮定すると、WIF モジュールの構成は次のようになります。

<modules>
  <!--other modules-->
  <add name="SessionAuthenticationModule" 
    type="Microsoft.IdentityModel.Web.SessionAuthenticationModule, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" 
    preCondition="managedHandler" />
  <add name="WSFederationAuthenticationModule" 
    type="Microsoft.IdentityModel.Web.WSFederationAuthenticationModule, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" 
    preCondition="managedHandler" />
</modules>

既定では、この構成を使用すると、ASP.NET パイプラインで処理するよう明示的にマップされた拡張子 (.aspx、.asax など) を持つリソースだけを保護します。他のリソースをフェデレーション認証で保護するには、IIS でこれらの拡張子を ASP.NET パイプラインにマップするか、次のように、modules 設定 (IIS 7 のみ) で runAllManagedModulesForAllRequests を true に設定できます。

<modules runAllManagedModulesForAllRequests="true">

FAM を起動するには、次のように、ASP.NET の認証モードを None に設定し、匿名ユーザーからのアプリケーション リソースへのアクセスを拒否します。

<authentication mode="None" />

<authorization>
  <deny users="?" />
</authorization>

どちらのモジュールでも、図 4 に示した ID モデルの構成設定を使用します。構成設定の典型的な例を図 5 に示します。certificateValidation の設定や federatedAuthentication 内のいくつかの設定を除き、ここに示す設定のほとんどは FedUtil によって自動的に生成されます。通常は、証明書の検証モードに PeerTrust を使用することをお勧めします。このモードは、ローカル コンピューターの TrustedPeople ストアに、信頼された証明書 (信頼された発行者の証明書など) をすべて明示的に追加することを表します。

図 5 パッシブ フェデレーション向けの ID モデルの構成

<microsoft.identityModel>
  <service>
    <issuerNameRegistry type="Microsoft.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
      <trustedIssuers>
        <add thumbprint="EF38A0A6D1274766093D3D78BFE4ECA77C62D5C3" 
          name="http://localhost:60768/STS/" />
      </trustedIssuers>
    </issuerNameRegistry>
    <certificateValidation certificateValidationMode="PeerTrust" 
      revocationMode="Online" trustedStoreLocation="LocalMachine"/>
    <audienceUris>
      <add value="http://localhost:50652/ClaimsAwareWebSite2/" />
    </audienceUris>
    <federatedAuthentication>
      <wsFederation passiveRedirectEnabled="true" 
        issuer="http://localhost:60768/STS/" 
        realm="http://localhost:50652/ClaimsAwareWebSite2/" 
        requireHttps="true" />
      <cookieHandler requireSsl="true" name="FedAuth"  
        hideFromScript="true" path="/ClaimsAwareWebSite2" />
    </federatedAuthentication>
    <serviceCertificate>
      <certificateReference x509FindType="FindByThumbprint" 
        findValue="8A90354199D284FEDCBCBF1BBA81BA82F80690F2" 
        storeLocation="LocalMachine" storeName="My" />
    </serviceCertificate>
  </service>
 </microsoft.identityModel>

一般に、パッシブ フェデレーションで発行済みのベアラー トークンを man-in-the-middle 攻撃から保護するには HTTPS/SSL が必要で、セッション Cookie にも HTTPS/SSL が必要です。既定では Cookie がスクリプトから認識されませんが、このように設定することは重要です。図 5 でこの設定を指定しているのはこのためです。

Cookie の名前とパスについては、名前は既定で FedAuth に設定され、パスは既定でアプリケーション ディレクトリに設定されます。Cookie に一意名を指定することは、ソリューション内の多数の RP アプリケーションで同一ドメインを共有している場合は特に便利です。逆に、同一ドメインの複数のアプリケーションで Cookie を共有する場合は、汎用パスを指定することもできます。

通常は、FedUtil を使用して、FAM と SAM を使用したパッシブ フェデレーション向けに ASP.NET アプリケーションを構成してから、ソリューションの要件に応じて適切な設定を調整します。また、FAM の代わりに FederatedPassiveSignIn コントロールを使用することもできます (図 3 参照)。このコントロールでは、設定を microsoft.identityModel セクションから読み込むことも、コントロール上で直接プロパティを設定することもできます。

このコントロールの手法が役に立つのは、ユーザーがコントロールをクリックすると明示的にサインインできるログイン ページに、未承認の要求をリダイレクトする (FAM によって自動的に STS にリダイレクトされない) 場合です。たとえば、ユーザーが 1 つの ID プロバイダー (ホーム領域) 以外の ID プロバイダーに属している可能性がある場合、ログイン ページでは、STS にリダイレクトする前にユーザーが自身のホーム領域を指定するメカニズムを提供できます。ホーム領域の検出については、後で説明します。

パッシブ トークンの発行

既に説明したように、パッシブ フェデレーションは、HTTP GET と POST、およびブラウザーのリダイレクトを使用して、RP と STS の間のコミュニケーションを容易にします。図 6 に、この処理のサインイン要求とサインイン応答で使用する主な要求パラメーターを示します。

パッシブ フェデレーションの要求に関与する主なサインイン要求とサインイン応答のパラメーター

図 6 パッシブ フェデレーションの要求に関与する主なサインイン要求とサインイン応答のパラメーター

STS は、サインイン要求を受信すると、wtrealm パラメーターを既知の RP 領域の一覧と照合して、RP に関する情報を把握しているかどうか検証します。おそらく、STS では RP に関する事前情報、トークンの暗号化に必要な証明書、および発行されるトークンに含める適切なクレームについて期待するデータを把握しているでしょう。RP でオプションの wreq パラメーターに完全なサインイン要求を指定すると必要なクレームを指定できるので、STS では、必要に応じて RP 領域の一覧を優先することも、認証済みユーザーに基づいて付与するクレームを自律的に決定することもできます。

簡単なフェデレーション シナリオ (図 1 参照) には、1 つの RP と、ユーザーを認証する 1 つの IP-STS があります。IP-STS が Windows ドメインに対してユーザーを認証する場合、Administrators、Users、Guest などのロール クレームが発行されることがあります。これは、これらのロールが、承認用 RP に対して意味があることを前提としています。ここからは、これらのロールが前提を満たしていると想定して、承認の手法を説明します。その後、必要に応じて STS クレームをより承認に便利な別の形式に変換する、RP でのクレームの変換について説明します。

クレームベースの承認

前回の記事で説明したように、.NET Framework のロールベースのセキュリティは、セキュリティ プリンシパルが各スレッドにアタッチされているという前提に基づいています。セキュリティ プリンシパルは IPrincipal に基づいており、認証済みユーザーの ID を IIdentity 実装にラップします。WIF は、IClaimsPrincipal と IClaimsIdentity に基づく ClaimsPrincipal 型と ClaimsIdentity 型を提供します (これらの型は最終的には IPrincipal と IIdentity から派生されます)。FAM では、サインイン応答の処理時に、発行されたセキュリティ トークンの ClaimsPrincipal を作成します。同様に、SAM ではセッション Cookie の ClaimsPrincipal を作成します。この ClaimsPrincipal は、ASP.NET アプリケーションでの WIF 承認の中核です。

次のいずれかの承認手法を使用できます。

  • 場所固有の承認設定を使用して、ディレクトリや個別のアプリケーション リソースへのアクセスを制限する。
  • ASP.NET のログイン用コントロール (LoginView コントロールなど) を使用して、機能へのアクセスを制御する。
  • ClaimsPrincipal を使用して、動的な IsInRole チェックを実行する (UI 要素の表示/非表示を動的に切り替えるなど)。
  • PrincipalPermission 型を使用して、動的なアクセス許可の要求を実行するか、特定のメソッドで宣言によるアクセス許可の要求が適していると思える場合は PrincipalPermissionAttribute を使用する。
  • 要求されたリソースにアクセスする前でも、カスタム ClaimsAuthorizationManager を提供して、アクセス確認を 1 つのコンポーネントに一元化する。

この中の 3 つのオプションは、ClaimsPrincipal 型によって公開される IsInRole メソッドを使用します。IsInRole チェックに適したクレームの種類としてロールを選択し、アクセス制御に適切なクレームが使用されるようにする必要があります。WIF の既定のロール クレームの種類は、次のとおりです。

https://schemas.microsoft.com/ws/2008/06/identity/claims/role

ClaimsPrincipal に定義済みのクレームが含まれていれば、ロールのクレームの種類は既定値と一致します。アクセス許可のクレームについては、後でクレームの変換との関連で説明します。アクセス許可のクレームを使用する場合は、IsInRole が機能するよう、クレームの種類をアクセス許可ではなくロールとして指定します。

特定のページやディレクトリへのアクセスは、web.config ファイルでグローバルに制御できます。アプリケーション ルートで、保護対象のパスを指定する location タグを指定し、アクセス可能なロールの一覧を許可して、その他すべてのユーザーのアクセスを拒否します。次の構成では、Administrators だけが AdminOnly ディレクトリに含まれるファイルにアクセスできるようになります。

<location path="AdminOnly">
  <system.web>
    <authorization>
      <allow roles="Administrators" />
      <deny users="*"/>
    </authorization>
  </system.web>
</location>

また、任意のサブディレクトリに web.config ファイルを配置して、承認の規則を指定することもできます。次の構成を AdminOnly ディレクトリに配置しても、前の例と同じ効果が得られます。

<configuration>
  <system.web>
    <authorization >
      <allow roles="Administrators" />
      <deny users="*"/>
    </authorization>
  </system.web>
</configuration>

UI コンポーネントの表示/非表示を動的に切り替えるなどの方法でページ内の機能へのアクセスを制御するには、LoginView など、コントロールのロールベースの機能を利用できます。ただし、大半の開発者は、ページの読み込み中にコントロールのアクセス制御用プロパティを明示的に設定して、より細かく制御する方を好みます。そのためには、ClaimsPrincipal で公開されている IsInRole メソッドを呼び出します。次のように Thread.CurrentPrincipal 静的プロパティを使用すると、現在のプリンシパルにアクセスできます。

if (!Thread.CurrentPrincipal.IsInRole("Administrators"))
  throw new SecurityException("Access is denied.");

実行時の明示的な IsInRole チェックとは別に、PrincipalPermission 型を使用して、従来のロールベースのアクセス許可の要求を作成することもできます。次のように、必要なロール クレーム (コンストラクターの 2 番目のパラメーター) でこの型を初期化すると、Demand の呼び出し時に現在のプリンシパルの IsInRole メソッドが呼び出されます。クレームが見つからないと、例外がスローされます。

PrincipalPermission p = 
  new PrincipalPermission("", "Administrators");
p.Demand();

この手法は、適切なロールが提供されない場合に要求を拒否するのに便利です。

要求されるすべてのリソースに共通する承認チェックを一元化するのにも便利です。場合によっては、アクセス制御ポリシー (データベースに格納された規則など) を設定していれば、このような規則を読み取って機能へのアクセスを制御する集中管理型のコンポーネントを使用できます。そのために、WIF では拡張可能な ClaimsAuthorizationManager コンポーネントを提供しています。前回の記事で説明したとおり、次のように、ID モデルに関するセクションでこのようなカスタム コンポーネントを構成できます。

<microsoft.identityModel>
  <service>
    <!--other settings-->
    <claimsAuthorizationManager 
      type="CustomClaimsAuthorizationManager"/>
  </service>
</microsoft.identityModel>

図 7 に、カスタム ClaimsAuthorizationManager を示します。このコンポーネントでは、種類が名前のクレームの有無を検証し、かつ要求されたリソースが AdminOnly ディレクトリに含まれていれば Administrators ロール クレームを必要とします。

図 7 カスタム ClaimsAuthorizationManager の実装

public class CustomClaimsAuthorizationManager: 
  ClaimsAuthorizationManager {

  public CustomClaimsAuthorizationManager()
  { }

  public override bool CheckAccess(
    AuthorizationContext context) {

    ClaimsIdentity claimsIdentity = 
      context.Principal.Identity as ClaimsIdentity;
    if (claimsIdentity.Claims.Where(
      x => x.ClaimType == ClaimTypes.Name).Count() <= 0)
      throw new SecurityException("Access is denied.");
        
    IEnumerable<Claim> resourceClaims = 
      context.Resource.Where(x=>x.ClaimType==ClaimTypes.Name);
    if (resourceClaims.Count() > 0) {
      foreach (Claim c in resourceClaims) {
        if (c.Value.Contains("\AdminOnly") && 
          !context.Principal.IsInRole("Administrators"))
          throw new SecurityException("Access is denied.");
      }
    }

    return true;
  }
}

CustomClaimsAuthorizationManager は、この機能を提供するために CheckAccess メソッドをオーバーライドします。このメソッドは AuthorizationContext パラメーターを提供します。AuthorizationContext パラメーターは、要求操作 (パッシブ フェデレーションの場合は、GET、POST などの HTTP 動詞)、要求されたリソース (URI)、および要求スレッドにまだ割り当てられていない ClaimsPrincipal に関する情報を提供します。

クレームの変換

IP-STS によって発行されたクレームは、認証済みユーザーの説明には便利ですが、多くの場合は RP の承認要件と関連していません。各 RP での承認に必要なロールの種類、アクセス許可、またはその他の詳細情報を確認することは、IdP の役割ではありません。IdP の役割は、ID プロバイダーのドメインに関連するクレーム、つまり IdP が認証済みユーザーに関してアサートできるクレームを付与することです。

したがって、RP では、IP-STS のクレームを承認との関連性が高い別の形式に変換しなければならないことがあります。つまり、RP では、ユーザー ID を (おそらくユーザー名や UPN ごとに) RP クレームのセットにマップすることになります。IP-STS で既定のロール クレームが付与されると仮定して、図 8 に、受信した各ロール クレームに応じて RP で発行される可能性のあるアクセス許可クレームのセットを示します。アクセス許可のクレームの種類は、次のように RP によって定義されたカスタムのクレームの種類になることもあります。

urn:ClaimsAwareWebSite/2010/01/claims/permission

IP-STS の受信クレームの変換には、カスタム ClaimsAuthenticationManager を使用することをお勧めします。次の構成設定を microsoft.identityModel セクションに追加することによって、カスタム ClaimsAuthenticationManager をインストールできます。

<microsoft.identityModel>
  <service>
    <!--other settings-->
    <claimsAuthenticationManager 
      type="CustomClaimsAuthenticationManager"/>
  </service>
</microsoft.identityModel>

図 9 に、サンプルの CustomClaimsAuthenticationManager を示します。このサンプルでは、IP-STS によって付与された受信ロール クレームを、RP に関連するアクセス許可クレームに変換します。

図 8 RP でのロール クレームからアクセス許可クレームへの変換

ロール クレーム アクセス許可クレーム
Administrators Create、Read、Update、Delete
Users Create、Read、Update
Guest Read

図 9 RP でのクレームのカスタム変換

public class CustomClaimsAuthenticationManager: 
  ClaimsAuthenticationManager {

  public CustomClaimsAuthenticationManager() { }

  public override IClaimsPrincipal Authenticate(
    string resourceName, IClaimsPrincipal incomingPrincipal) {

    IClaimsPrincipal cp = incomingPrincipal;
    ClaimsIdentityCollection claimsIds = 
      new ClaimsIdentityCollection();

    if (incomingPrincipal != null && 
      incomingPrincipal.Identity.IsAuthenticated == true) {

      ClaimsIdentity newClaimsId = new ClaimsIdentity(
        "CustomClaimsAuthenticationManager", ClaimTypes.Name, 
        "urn:ClaimsAwareWebSite/2010/01/claims/permission");

      ClaimsIdentity claimsId = 
        incomingPrincipal.Identity as ClaimsIdentity;
      foreach (Claim c in claimsId.Claims)
        newClaimsId.Claims.Add(new Claim(
          c.ClaimType, c.Value, c.ValueType, 
          "CustomClaimsAuthenticationManager", c.Issuer));

      if (incomingPrincipal.IsInRole("Administrators")) {
        newClaimsId.Claims.Add(new Claim(
          "urn:ClaimsAwareWebSite/2010/01/claims/permission", 
          "Create"));
        newClaimsId.Claims.Add(new Claim(
          "urn:ClaimsAwareWebSite/2010/01/claims/permission", 
          "Read"));
        newClaimsId.Claims.Add(new Claim(
          "urn:ClaimsAwareWebSite/2010/01/claims/permission", 
          "Update"));
        newClaimsId.Claims.Add(new Claim(
          "urn:ClaimsAwareWebSite/2010/01/claims/permission", 
          "Delete"));
      }

      else if (incomingPrincipal.IsInRole("Users")) {
        newClaimsId.Claims.Add(new Claim(
          "urn:ClaimsAwareWebSite/2010/01/claims/permission", 
          "Create"));
        newClaimsId.Claims.Add(new Claim(
          "urn:ClaimsAwareWebSite/2010/01/claims/permission", 
          "Read"));
        newClaimsId.Claims.Add(new Claim(
          "urn:ClaimsAwareWebSite/2010/01/claims/permission", 
          "Update"));
      }

      else {
        newClaimsId.Claims.Add(new Claim(
          "urn:ClaimsAwareWebSite/2010/01/claims/permission", 
          "Read"));
      }

      claimsIds.Add(newClaimsId);
      cp = new ClaimsPrincipal(claimsIds);
    }

    return cp;
  }
}

IsInRole チェック (前述) を機能させるには、クレームの種類をアクセス許可ではなくロールとして指定する必要があります。図 9 では、RP が ClaimsIdentity を作成しているため、ClaimsIdentity の作成時にクレームの種類にアクセス許可が指定されています。

クレームのソースが受信 SAML トークンの場合、クレームの種類としてロールを SecurityTokenHandler に指定できます。次のコードは、Saml11SecurityTokenHandler を宣言によって構成し、クレームの種類をアクセス許可ではなく、ロールとして使用する方法を示しています。

<microsoft.identityModel>
  <service>
    <!--other settings-->
    <securityTokenHandlers>
      <remove type="Microsoft.IdentityModel.Tokens.Saml11.Saml11SecurityTokenHandler, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
      <add type="Microsoft.IdentityModel.Tokens.Saml11.Saml11SecurityTokenHandler, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
        <samlSecurityTokenRequirement >
          <roleClaimType 
            value= "urn:ClaimsAwareWebSite/2010/01/claims/permission"/>
        </samlSecurityTokenRequirement>
      </add>
    </securityTokenHandlers>
  </service>
</microsoft.identityModel>

SAML トークン ハンドラーには samlSecurityTokenRequirement セクションがあり、このセクション内で、クレームの種類に名前やロールを設定できるだけでなく、証明書の検証や Windows トークンに関するその他の設定を指定できます。

ホーム領域の検出

ここまでは、1 つの IP-STS を使用するシンプルなフェデレーション シナリオを取り上げました。このシナリオでは、RP が必ず特定の IP-STS にリダイレクトしてユーザーを認証することを前提としています。

しかし、フェデレーション環境では、複数のドメインに属する複数のトークン発行者を RP が信頼している場合があります。この場合、RP はリソースへのアクセスを要求しているユーザーをどの IP-STS で認証するか決定しなければならないため、新たな課題が浮かび上がります。ユーザーを認証するドメインがユーザーのホーム領域と呼ばれていることから、この作業をホーム領域の検出と呼びます。

アプリケーションからホーム領域を検出するメカニズムは多数あります。

  • ここまでの例のように、ホーム領域が事前に判明している場合、要求は必ず特定の IP-STS にリダイレクトされます。
  • ユーザーが別のポータルから RP を参照する場合、そのポータルのユーザーのホーム領域を示すクエリ文字列を指定できます。
  • RP がユーザーに対しホーム領域ごとに特定の閲覧開始ページにアクセスするよう要求する場合、ランディング ページは特定のホーム領域を想定できます。
  • RP は、要求の IP アドレスなどのヒューリスティックを使用してホーム領域を特定できることがあります。
  • 上記のいずれの手法でも RP がホーム領域を特定できない場合、ユーザーがホーム領域を選択したり、RP のホーム領域の特定に役立つ情報を指定したりすることができる UI を表示できます。
  • RP が情報カードをサポートしている場合、選択したカードに応じて、アクティブ フェデレーションを使用して適切なホーム領域で認証できます。
  • WS-Federation にはホーム領域を解決する検出サービスの実装方法が簡単に記載されていますが、この方法に関して明確に定義された仕様はありません。

どの方法でホーム領域を検出する場合でも、その目的は、適切な IP-STS で認証されるようにユーザーをリダイレクトすることです。ホーム領域の検出には、いくつかのシナリオが考えられます。サインイン要求が適切な IP-STS に送信されるよう、RP が発行者の URI を動的に設定する必要があるシナリオもあります。この場合、RP はすべての信頼された IP-STS の一覧を trustedIssuers セクションに指定する必要があります。たとえば、次のように指定します。

<trustedIssuers>
  <add thumbprint="6b887123330ae8d26c3e2ea3bb7a489fd609a076" 
    name="IP1" />
  <add thumbprint="d5bf17e2bf84cf2b35a86ea967ebab838d3d0747" 
    name="IP2" />
</trustedIssuers>

また、FAM が公開している RedirectingToIdentityProvider イベントをオーバーライドすると、関連するヒューリスティックを使用して、STS に適した URI を特定できます。これを行うには、次のコードを Global.asax 実装に配置します。

void WSFederationAuthenticationModule_RedirectingToIdentityProvider(
  object sender, RedirectingToIdentityProviderEventArgs e) {
  if (e.SignInRequestMessage.RequestUrl.Contains(
    "IP1RealmEntry.aspx")) {
    e.SignInRequestMessage.BaseUri = 
      new Uri("https://localhost/IP1/STS/Default.aspx");
  }
  else if (e.SignInRequestMessage.RequestUrl.Contains(
    "IP2RealmEntry.aspx")) {
    e.SignInRequestMessage.BaseUri = new Uri(
       "https://localhost/IP2/STS/Default.aspx");
  }
}

ホーム領域を示すパラメーター (whr) をサインイン要求と共にプライマリ STS に渡すシナリオもあります。たとえば、RP には、クレームの変換を実行するリソース STS (R-STS または RP-STS) がある場合があります。RP-STS はユーザーを認証しませんが (IdP ではないため)、1 つ以上の他の IdP と信頼関係を確立しています。

RP は RP-STS と信頼関係を確立しており、RP-STS によって発行されたトークンを必ず優先します。RP-STS は、要求ごとに適切な IdP へのリダイレクトを実行します。前のコードに示したとおり、RP-STS ではリダイレクト先として適切な IP-STS を特定できますが、RP はホーム領域に関する情報を指定し、ホーム領域を示すパラメーターにこの情報を格納して RP-STS に渡すこともできます。この場合に RP がホーム領域を示すパラメーターを動的に設定する方法は次のとおりです。

void WSFederationAuthenticationModule_RedirectingToIdentityProvider(
  object sender, RedirectingToIdentityProviderEventArgs e) {
  if (e.SignInRequestMessage.RequestUrl.Contains(
    "IP1RealmEntry.aspx")) {
    e.SignInRequestMessage.HomeRealm = 
      "https://localhost/IP1/STS/Default.aspx";
  }
  else if (e.SignInRequestMessage.RequestUrl.Contains(
    "IP2RealmEntry.aspx")) {
    e.SignInRequestMessage.HomeRealm = 
      "https://localhost/IP2/STS/Default.aspx";
  }
}

RP-STS では、このパラメーターを使用して適切な IP-STS にリダイレクトしてから、IP-STS のクレームを RP に関連するクレームに変換します。

シングル サインオンとシングル サインアウト

シングル サインオンとシングル サインアウトは、フェデレーションの重要な要素です。シングル サインオンは、1 回認証するだけで、認証済みユーザーが複数の RP アプリケーションにアクセスできるようにする機能です。シングル サインアウトは、名前のとおり、すべての RP アプリケーションとアプリケーションに関連する一連の STS から 1 回の要求でサインアウトできるようにします。

シンプルなフェデレーション シナリオ (図 1 参照) では、ユーザーは IP-STS に認証され、発行されたセキュリティ トークンに基づいて RP で承認されます。認証後、ユーザーは STS 用のセッション Cookie と RP 用のセッション Cookie を保有します。ここでユーザーが別の RP を参照する場合、どちらの RP アプリケーションでも同じ IP-STS を信頼しているとすると、ユーザーは認証のために同じ IP-STS にリダイレクトされます。ユーザーが IP-STS とのセッションを既に確立しているため、STS は資格情報の入力を求めることなく 2 つ目の RP のトークンを発行します。これで、ユーザーは 2 つ目の RP にアクセスできるようになり、2 つ目の RP 用の新しいセッション Cookie を保有するようになります。

既に説明したように、WIF には、認証済みユーザーのセッション Cookie を生成する SAM が用意されています。既定では、このセッション Cookie はドメインの相対アプリケーション アドレスに対して発行され、セッション Cookie のベース名は FedAuth です。フェデレーション セッション Cookie は大きなサイズになることがあるため、通常、トークンは 2 つ (またはそれ以上) の Cookie に分割されます (FedAuth、FedAuth1 など)。

同一ドメインで複数のアプリケーションをホストしている場合、フェデレーション シナリオの一環として、既定の動作ではブラウザーが各 RP 用の FedAuth Cookie を保持することになります (図 10 参照)。ブラウザーは、要求のドメインとパスに関連付けられた Cookie だけを送信します。

各 RP と STS に関連付けられたセッション Cookie

図 10 各 RP と STS に関連付けられたセッション Cookie

一般に既定の動作で問題はありませんが、場合によっては (特に同一ドメインでアプリケーションをホストしている場合)、セッション Cookie ごとにアプリケーション単位の一意名を提供する必要があります。また、同一ドメインの複数のアプリケーションでセッション Cookie を共有している場合もあります。この場合は、Cookie のパスを "/" に設定できます。

セッション Cookie が期限切れになると、ブラウザーによってその Cookie がキャッシュから削除され、ユーザーは認証のために再度 STS にリダイレクトされます。別途、セッション Cookie に関連付けられている発行済みのトークンが期限切れになると、WIF は、新しいトークン取得のために STS にリダイレクトします。

サインアウトはもっと明示的に、通常はユーザーの操作によって行われます。シングル サインアウトは、WS-Federation 仕様のオプション機能です。WS-Federation 仕様では、STS がトークンを発行した他の RP アプリケーションにも、STS からサインアウト要求を通知するよう推奨しています。このようにすると、シングル サインオンのセッション中にユーザーが参照したすべてのアプリケーションからセッション Cookie が削除されます。複数の STS を使用する複雑なシナリオでは、サインアウト要求を受信するプライマリ STS から、同じ処理を実行するよう他の STS にも通知します。

今回の記事の目的から、ここではフェデレーション シングル サインアウトを容易にするために RP で実行する必要がある手順に重点を置いて説明します。FederatedPassiveSignInStatus コントロールは、サインインとサインアウトをサポートする任意のページに配置でき、その状態を自動的に通知します。サインインしたら、このコントロールにはサインアウト用のリンク、ボタン、またはイメージが表示されます。

ユーザーが FederatedPassiveSignInStatus コントロールをクリックすると、SignOutAction プロパティに応じてサインアウトが実行されます。このプロパティの設定は、Refresh、Redirect、RedirectToLoginPage、または FederatedPassiveSignOut のいずれかです。最初の 3 つの設定を選択すると、アプリケーション用のセッション Cookie が削除されますが、STS にサインアウト要求が通知されません。FederatedPassiveSignOut 設定を選択すると、FederatedPassiveSignInStatus コントロールで WSFederationAuthenticationModule の SignOut メソッドが呼び出されます。これにより、フェデレーション セッション Cookie がアプリケーションから削除されます。また、次のようなサインアウト要求が STS に送信されます。

GET https://localhost/IP1/STS?wa=wsignout1.0

FederatedPassiveSignInStatus コントロールを使用していない場合は、WSFederationAuthenticationModule.SignOut メソッドを直接呼び出して、サインアウト要求により STS へのリダイレクトをトリガーできます。

シングル サインアウトは、ユーザーが自身のフェデレーション ID でサインインしたすべてのアプリケーションからサインアウトすることを意味します。STS でシングル サインアウトをサポートする場合、ユーザーがセッション中にログインした RP アプリケーションの一覧を STS で保持して、フェデレーション サインアウトの要求時に各 RP に対して次のようなクリーンアップ要求を発行します。

GET https://localhost/ClaimsAwareWebSite?wa=wsignoutcleanup1.0

さらに複雑なシナリオでは、フェデレーション セッションに関与する他のすべての STS に同じクリーンアップ要求を送信します。そのために、STS ではすべての RP と STS のクリーンアップ URI を事前に把握している必要があります。シングル サインアウトをサポートするには、RP でこのようなクリーンアップ要求を処理できるようにしてください。クリーンアップ要求の処理は、FAM と FederatedPassiveSignInStatus コントロールの両方でサポートします。FAM を使用している場合は、クリーンアップ要求を RP の任意の URI に送信でき、FAM がこの要求を処理してすべてのセッション Cookie をクリーンアップします。FederatedPassiveSignInStatus コントロールを使用している場合は、このコントロールを含むページにクリーンアップ要求を送信する必要があります。

実際のところ、WS-Federation 仕様にはシングル サインアウトとクリーンアップの動作の実装方法が詳しく記述されておらず、推奨されるクエリ文字列とコミュニケーションの流れ程度です。このため、すべてのフェデレーション パートナーでシングル サインアウトを機能させることは簡単ではありません。しかし、フェデレーション環境を所有していてこの目標を達成するつもりなら、実現可能です。

Michele Leroux Bustamante は、IDesign (idesign.net、英語) のチーフ アーキテクトと BiTKOO (bitkoo.com、英語) のチーフ セキュリティ アーキテクトを兼任しています。また、サンディエゴ地域の Microsoft Regional Director であり、Connected Systems の Microsoft MVP でもあります。彼女のブログは、dasblonde.net(英語) で公開されています。

この記事のレビューに協力してくれた技術スタッフの Govind Ramanathan に心より感謝いたします。