Azure ACS の Custom ID Provider の作成 : OpenID 編


環境 :
Windows Azure Active Directory - Access Control Service 2.0
Windows Azure PowerShell Cmdlets 2.2
Visual Studio 2010
DotNetOpenAuth 4.0
Windows Azure SDK for .NET 1.6
WIF SDK 3.5 (Windows Identity Foundation SDK 3.5)

Azure Active Directory ACS における Custom STS (ID Provider) の作成

こんにちは。

以前投稿した「Azure ACS の Custom STS (ID Provider) の作成」では、WS-Federation / WS-Trust と SAML Assertion を使用して Custom Identity Provider (IdP) を構築し、これを Windows Azure Active Directory の Access Control Service (ACS) で使用しました。今週は、ソーシャルの認証・認可基盤として主流な OpenID、OAuth などを使用して、同じことをやってみます。なお、両方説明するのは大変なので、今回は OpenID を例に説明します。

今回、OpenID、OAuth の Custom Identity Provider (IdP) を作成するために、オープン ソースの .NET ライブラリーである DotNetOpenAuth を使用します。(NuGet でも取得できます。)
このライブラリーは、後述するように、プロバイダー用のクラス、Relying Party 用のクラスなどを持っていて、開発者 (プログラマー) は、OAuth や OpenID の難解なやりとりをコーディングしなくても、主要な処理を実装できます。(とは言え、OAuth などの基本的な流れは、ちゃんと抑えておいてくださいね。まったくプロトコルを理解せずにライブラリーを使用するのは、かなり苦しいと思います。)

無論、この DotNetOpenAuth を使えば Windows Azure Active Directory (の機能である Access Control Service, ACS) を使わず、.NET による OAuth、OpenID を使った SSO が簡単に実装できます。しかし、ご存じの通り、Windows Azure Active Directory (ACS) と組み合わせることで、アプリケーション側の変更をおこなうことなく (あるいは、省力の変更で)、さまざまな種類の IdP への対応や、複数の IdP に同時に対応したアプリケーションなどが構築できます。

 

Custom OpenID Provider の作成 (Programming)

DotNetOpenAuth をダウンロードするとわかりますが、多くサンプル コードが入っています。そこで、今回は、私の下手なコードで紹介するのではなく、サンプル コードの OpenIdProviderMvc プロジェクト をそのまま使用してみましょう。

サンプル コードの OpenIdProviderMvc は、ASP.NET MVC を使った Custom OpenID Provider のサンプル コードです。
この OpenID Provider の主要な処理は、OpenIdController の Provider アクション (/OpenId/Provider) に実装されています。(下記は、このアクションのコードの抜粋です。)
下記の通り、DotNetOpenAuth では、OpenIdProvider クラスが提供されており、Provider に関する複雑な処理は、すべてこのクラスが実行してくれます。(OpenID では、主に URI に必要な情報の多くが設定されますが、この URI の文字列を元に、必要な手続きを実行してくれます。)

特に、下記 太字の OpenIdProvider.PrepareResponse(request).AsActionResult() は削除しないでください。例えば、OpenID では、Web アプリケーション (Relying Party) が Provider から受け取った Signature (Response Signature) の妥当性を確認 (Verify) する際、Provider に接続をおこなうことがあります。このため、Provider 側では、こうした要求に対する処理も実装しておく必要がありますが、このメソッドでは、そうした細々とした処理をおこなってくれます。

. . .
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.OpenId;
using DotNetOpenAuth.OpenId.Behaviors;
using DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy;
using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration;
using DotNetOpenAuth.OpenId.Provider;
using DotNetOpenAuth.OpenId.Provider.Behaviors;
. . .

public class OpenIdController : Controller {
  internal static OpenIdProvider OpenIdProvider = new OpenIdProvider();

  [ValidateInput(false)]
  public ActionResult Provider() {
    IRequest request = OpenIdProvider.GetRequest();
    if (request != null) {
      // Some requests are automatically handled by DotNetOpenAuth.
      // If this is one, go ahead and let it go.
     if (request.IsResponseReady)
     {
       return OpenIdProvider.PrepareResponse(request).AsActionResult();
     }
. . .

続く処理 (下記) では、さまざまな種類の要求 (Request) に応じた確認 (条件分岐) をおこなっていますが、一般的な OpenID による認証要求の流れでは、下記の AskUser アクション (下記の太字) まで進みます。
AskUser アクションは、下記の通り Authorize 属性 (フィルター) の設定されたアクションであるため、ASP.NET MVC の一般的な流れに沿って、フォーム認証が処理され、ログイン画面 (/Account/LogOn) が利用者に表示されます。

. . .

public ActionResult Provider() {
  IRequest request = OpenIdProvider.GetRequest();
  if (request != null) {
    // Some requests are automatically handled by DotNetOpenAuth.
// If this is one, go ahead and let it go.
    if (request.IsResponseReady)
    {
      return OpenIdProvider.PrepareResponse(request).AsActionResult();
    }

    // This is apparently one
// that the host (the web site itself) has to respond to.
    ProviderEndpoint.PendingRequest = (IHostProcessedRequest)request;

    // Try responding immediately if possible.
    ActionResult response;
    if (this.AutoRespondIfPossible(out response)) {
      return response;
    }

    // We can't respond immediately with a positive result.
// But if we still have to respond immediately...
    if (ProviderEndpoint.PendingRequest.Immediate) {
      // We can't stop to prompt the user
// -- we must just return a negative response.
      return this.SendAssertion();
    }

    return this.RedirectToAction("AskUser");
  } else {
    . . .
  }
}

[Authorize]
public ActionResult AskUser() {
  . . .

利用者 (ユーザー) がログイン画面で認証処理をおこなうと、フォーム認証の一般的な動きによって、再度、AskUser アクション (上記) へと戻されます。(今度は、認証された状態で戻ってきます。) そして、AskUser アクションは、下記のような View を表示します。この画面 (View) では、呼び出し元の Web アプリケーション (Relying Party) へのリダイレクトをユーザーに確認しています。

上記のビューの [yes] ボタンを押すと、この OpenID Provider は、必要な情報を URI に設定して、呼び出し元のアプリケーション (Relying Party) にリダイレクトをおこないます。(この処理は、サンプル コードの AskUserResponse アクションに記述されています。)

先日実施された Windows Developer Days のセッション「敬遠していた認証/認可テクノロジーを 45 分で理解しちゃいましょう !」で エバンジェリスト安納 も解説してましたが、OpenID、OAuth などソーシャル アプリケーションにおける認可では、Identity Provider (IdP) が持つ情報をアプリケーション (Relying Party) に渡す際の責任は、利用者本人 (エンドユーザー自身) が負うことになります。このため、上記のような画面を提供して、どのような情報 (個人情報) をアプリケーションに渡すか、利用者に確認をおこなうようにしましょう。(これは、OpenID Provider の「マナー」のようなものです。)

上記の処理後、同じブラウザーを使用して、この OpenID Provider に 再度 認証を要求した場合には、フォーム認証によって書き込まれた Cookie (認証チケット) によって、ログイン画面の表示はスキップされます。つまり、SSO が実現できます。

補足 : なお、この OpenID Provider の Web.config では、下記の通り SSL を不要にしているので、通常の htp://... で接続をおこなうことができます。
<dotNetOpenAuth>
  <openid>
    <provider>
      <security requireSsl="false" />
    . . .

DotNetOpenAuth のライブラリー (DotNetOpenAuth.dll) を使って、OpenID Provider を使用する Relying Party も容易に構築できます。
例えば、上記の OpenID Provider を IIS にアプリケーションとして配置し (今回、配置先を http://test01.example.com/OpenIdProviderMvc と仮定します)、ASP.NET Web アプリケーションを新規作成して、ボタン (Button) を張り付け、下記の通りコードを記述すると、この OpenID Provider を使用する Web アプリケーション (Relying Party) が完成します。(このアプリケーションでは、ボタンを押すと OpenID Provider のサイトにリダイレクトされ、認証後、このアプリケーションに戻ってきます。下記の通り、OpenIdRelyingParty クラスを使って、非常に少ないコードで実装できます。)

. . .
using DotNetOpenAuth.OpenId.RelyingParty;
. . .

protected void Page_Load(object sender, EventArgs e)
{
    OpenIdRelyingParty openIdRP = new OpenIdRelyingParty();
    IAuthenticationResponse authRes = openIdRP.GetResponse();
    if ((authRes != null) &&
        (authRes.Status == AuthenticationStatus.Authenticated))
    {
         Label1.Text = authRes.ClaimedIdentifier.ToString();
    }
}

protected void Button1_Click(object sender, EventArgs e)
{
    OpenIdRelyingParty openIdRP = new OpenIdRelyingParty();
    UriBuilder builder = new UriBuilder(Request.Url) { Query = "" };
    IAuthenticationRequest req = openIdRP.CreateRequest(
        "http://test01.example.com/OpenIdProviderMvc/",
        builder.Uri,
        builder.Uri);
    req.RedirectToProvider();
}
. . .

補足 : 上記のアプリケーションでは、http://test01.example.com/OpenIdProviderMvc/OpenId/Provider ではなく、ルート (http://test01.example.com/OpenIdProviderMvc/) を指定しています。上記の CreateRequest メソッドは、この URI (Home/Index) にメタデータ (XML 形式の xrd ファイル) を要求し、このファイル (xrd) に記述されたエンドポイントの URI (この URI として /OpenId/Provider を返します) を確認して、そのエンドポイントに接続します。ここでは説明を省略しますが、付属のサンプルの OpenIdProviderMvc プロジェクトには、このためのコードも記述されています。(Google アカウントなど他の OpenID Provider でも、同様に、こうしたメタデータが提供されています。)

 

Custom OpenID Provider の公開 (Publish)

では、上記の Custom OpenID Provider を Windows Azure Active Directory (ACS) で使用してみましょう。

上述の通り、IdP (OpenID Provider) を利用する Web アプリケーション (Relying Party) は、受け取った署名 (openid.sig) の確認の際に、OpenID Provider に接続します。このため、OpenID では、AD FS のように、IdP (OpenID Provider) をオンプレミス環境に置いて連携させるという使い方はせず、必ずインターネット上に配置してください。今回使用する Custom OpenID Provider (上記の OpenIdProviderMvc) も、以下の手順で、Window Azure を使用して、インターネット上にホストしておきます。

まず、サンプルの OpenIdProviderMvc.csproj (上述) を Visual Studio で開きます。
ソリューション エクスプローラーでこのプロジェクトを右クリックして、[Add Windows Azure Deployment Project] (下図) を選択して、Windows Azure 用のプロジェクトを追加します。

あとは、いつもの要領で、このプロジェクトを Windows Azure に配置します。(配置の手順については省略します。)
以降では、http://customopenid.cloudapp.net の URL にホストされたと仮定して進めます。

補足 : 配置の際、プロジェクトで参照している DotNetOpenAuth.dll の [ローカル コピー] プロパティ (下図) を、True に設定しておいてください。

 

Windows Azure Active Directory (ACS) への Custom OpenID Provider (IdP) の登録

つぎに、上記の Custom OpenID Provider を、Windows Azure Active Directory (ACS) に登録します。

いつものように、Access Control Service (ACS) の管理ポータルにログインし、Access Control Service の名前空間を新規作成します。(今回は、作成した名前空間を「openidtest」と仮定します。)

さて、ACS の管理画面を見ていただくとわかりますが、カスタムの OAuth や OpenID のプロバイダーは、実は、管理画面を使った設定は不可能です。これらのプロバイダーは、実は、プログラム コードWindows Azure PowerShell Cmdlets を使って追加できます。(これ、私も知らなかったのですが、以前、アークウェイの福井さんに教えて頂きました。つまり、管理 API のみで、設定可能ということです。)
今回は、Windows Azure PowerShell Cmdlets を使用して、下記の通り、上記の Custom OpenID Provider を登録します。(下記の Management Key は、ACS の管理ポータルを表示し、[管理サービス] を選択して、設定されている [既定の管理サービス アカウント] の [対称キー] の値をコピーしてください。)

Add-IdentityProvider -Namespace "openidtest" -ManagementKey "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx="
-Type "Manual" -Name "CustomOpenIdProvider" -Protocol OpenId
-SignInAddress "http://customopenid.cloudapp.net/OpenId/Provider"

この際、上記の通り、サーバーのエンドポイントである http://.../OpenIdProviderMvc/OpenId/Provider のアドレスを設定する必要があるので注意してください。

 

Web Application (RP) の作成

つぎに、上記の Custom IdP を使用する Web アプリケーション (Relying Party, RP) を作成します。

この作成手順は、10 行シリーズの「Windows Azure Access Control Service (ACS) を利用する」 に記載されていますので、参照してください。(ここでは、この手順の説明は省略します。)
ざっくりと、以下の手順になります。

  • Web アプリケーション (RP) を構築 (プログラミング) します。
  • WIF (Windows Identity Foundation) を使用して、Web アプリケーションに必要な構成をおこないます。(証明書の設定なども必要です。)
  • Web アプリケーション を Windows Azure のホステッド サービス (Hosted Services) に配置します。
  • ACS の管理画面を使って、上記の名前空間 (openidtest) に、この Web アプリケーション (RP) を登録します。
  • ACS の管理画面を使って、規則グループ (クレーム ルール) などを設定します。

補足 : [STS 参照の追加] が表示されない場合は、%programefiles%\Windows Identity Foundation SDK\v3.5\FedUtil.exe を直接起動してください。

補足 : ASP.NET 4.0 以上でない場合は、Web.config への以下の追加は不要です。(下記の requestValidationMode 属性は、.NET 4 以降で使用可能です。)
<httpRuntime requestValidationMode="2.0" />

 

動作確認

では、動作を確認してみましょう。

Web アプリケーション (RP) に接続をおこなうと、下図の通り、IdP の Selector が表示されます。(ただし、CustomOpenIdProvider のみを設定している場合は、この表示はスキップされ、直接、OpenID Provider のサイトにリダイレクトされます。)

上図で [CustomOpenIdProvider] を選択すると、さきほど Windows Azure 上に配置した Custom OpenID Provider のエンドポイント (http://customopenid.cloudapp.net/OpenId/Provider) にリダイレクトされ、さらに、フォーム認証によって、下図の通り、ログイン画面が表示されます。

上記で、ID / パスワードを入力すると、認証に成功し、下図の通り、Redirect を確認する画面が表示されます。
[yes] ボタンを押すと、以降は、Windows Azure Active Directory (ACS) のサイトにリダイレクトされて、最終的に、Web アプリケーション (Relying Party, RP) にリダイレクトされます。Web アプリケーション (RP) では、内部で受け取ったトークンを検証し、アプリケーションを表示します。

前述の通り、Passive のシングル サインオン (Passive SSO) も実現されています。
Custom OpenID Provider では、FormsAuthentication の認証チケット (Cookie) が設定されています。このため、同じブラウザーを使って、再度、Web アプリケーション (RP) に接続したり、この ACS の名前空間 (openid) を使用する別の Web アプリケーション (RP) に接続すると、上記のログイン画面は表示されず、リダイレクトを確認する画面のみが表示されます。

実際の開発では、許可された RP 以外の接続を拒否する機能を追加したり、確認画面についても、一度 利用者が [yes] を押したら、以降は表示をスキップするなど、要件にあわせて、プログラミング (カスタマイズ) をおこなってください。(このサンプルでは、すべての RP を受け付けます。また、毎回、上記の確認画面が表示されます。)

 

Claim 情報の受け渡し

上記の付属の OpenIdProviderMvc のサンプル コードには、下記の通り、Claim Request として EMail Address の要求があった場合、適当な EMail Address の値 (bob3@dotnetopenauth.net など) を返すように実装されています。

[OpenIdController.cs]

. . .
public class OpenIdController : Controller {
  . . .

  public ActionResult SendAssertion() {
    var pendingRequest = ProviderEndpoint.PendingRequest;
    var authReq = pendingRequest as IAuthenticationRequest;
    var anonReq = pendingRequest as IAnonymousRequest;
    . . .

    if ((authReq != null && authReq.IsAuthenticated.Value) ||
      (anonReq != null && anonReq.IsApproved.Value)) {

      var claimsRequest = pendingRequest.GetExtension<ClaimsRequest>();
      if (claimsRequest != null) {
        var claimsResponse = claimsRequest.CreateResponse();

        if (claimsRequest.Email != DemandLevel.NoRequest) {
          claimsResponse.Email = User.Identity.Name + "@dotnetopenauth.net";
        }

        pendingRequest.AddResponseExtension(claimsResponse);
      }
    }

    return OpenIdProvider.PrepareResponse(pendingRequest).AsActionResult();
  }
  . . .

そこで、例えば、ACS の管理画面を使って、下図のように、規則グループ (Rule) に http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress をパスするように要求すると、Web アプリケーション (RP) への EMail Address の受け渡しが可能です。(同様に、name, nameidentifier などの設定も可能です。)

Web アプリケーション (RP) 側では、下記の通り、OpenID Provider から渡された EMail Address を取得できます。

. . .

public class HomeController : Controller
{
    public ActionResult Index()
    {
        IClaimsPrincipal cp = HttpContext.User as IClaimsPrincipal;
        IClaimsIdentity id = (IClaimsIdentity)cp.Identity;
        ViewData["email_test"] = (from c in id.Claims
            where c.ClaimType == ClaimTypes.Email
            select c.Value).SingleOrDefault();

        return View();
    }
    . . .

 

今回は OpenID を例に説明しましたが、同様に、DotNetOpenAuth には OAuth Provider のサンプル コードなども入っています。.NET でソーシャル系のアプリケーションを構築 (公開) する際などに、是非活用してみてください。

 

Windows Azure Programming How-To

Windows Azure のトライアル (無償試用) について

 

Comments (0)

Skip to main content