NerdDinnerチュートリアル

NerdDinnerステップ9:認証と承認

Scott Guthrie 著/Chica
2010/02/26

 本記事は、Microsoftの本社副社長であり、ASP.NETやSilverlightなどの開発チームを率いるScott Guthrie氏が公開している「NerdDinner Tutorial」を翻訳したものです。氏の許可を得て転載しています。

[これは無償の"NerdDinner"アプリケーション・チュートリアルのステップ9で、ASP.NET MVCを使用して、小さいながらも完全なWebアプリケーションを構築する手順を紹介しています。]

 現在、NerdDinnerアプリケーションは誰もがサイトを訪れて、どんな夕食会の詳細でも作成して編集できます。さらに変更して、新しい夕食会を作成するためにはユーザーは登録してサイトにログインする必要があり、制限を追加して、夕食会をホストしているユーザーだけが、後でそれを編集できるようにします。

 これを可能にするには、認証(Authenticaion)と承認(Authorization)を使ってアプリケーションにセキュリティを適用します。

認証と承認の理解

 認証とは、アプリケーションにアクセスしているクライアントの身分を確認して検証するプロセスです。もっと簡単にいうと、エンドユーザーがWebサイトを訪れたときに“誰であるか”を確認することです。ASP.NETは、ブラウザのユーザーを認証する方法を複数サポートしています。インターネットのWebアプリケーションで一番よく使用されている認証方法は、“フォーム認証”と呼ばれるものです。フォーム認証では、開発者がアプリケーション内でHTMLのログイン・フォームを持つことができ、エンドユーザーが送信したユーザー名/パスワードを、データベースまたは、そのほかのパスワード証明の保存場所に対して検証します。もしユーザー名/パスワードの組み合わせが正しければ、開発者はASP.NETに暗号化されたHTTPクッキーを発行してもらい、以降のリクエストでユーザーが認識されるようにできます。NerdDinnerアプリケーションではフォーム認証を使用します。

 承認は、認証されたユーザーが特定のURL/リソースへのアクセス権または実行権があるかを確認するプロセスです。例えば、NerdDinnerアプリケーションでは、ログインしたユーザーだけが/Dinners/Create URLにアクセスして、新しい夕食会を作成できるように承認を行います。また、承認ロジックを追加して、夕食会をホストしているユーザーだけがその編集を行え、そのほかのすべてのユーザーは編集のアクセスが拒否されるようにしていきます。

フォーム認証とAccountController

 ASP.NET MVCのデフォルトのVisual Studioプロジェクト・テンプレートでは、新しいASP.NET MVCアプリケーションが作成されると自動的にフォーム認証が可能になります。また自動的に、事前にビルドされたアカウント・ログイン・ページの実装がプロジェクトに追加され、それによりサイト内のセキュリティの統合が非常に簡単です。

 ユーザーが認証されていないと、デフォルトのSite.masterマスター・ページは“Log On”リンクをサイトの右上に表示します。


図1

 “Log On”リンクをクリックすると、ユーザーは/Account/LogOn URLに移動します。


図2

 未登録のユーザーが“Register”リンクをクリックすると、/Account/Register URLに移動し、アカウントの詳細が入力できるようになります。


図3

 “Register”ボタンをクリックすると、ASP.NETメンバーシップ・システムで新しいユーザーが作成され、そのユーザーはフォーム認証を使用して、そのサイトで認証されます。

 ユーザーがログインすると、Site.masterはページの右上に“Welcome [username]!”というメッセージを出力し、“Log On”の代わりに、“Log Off”リンクを描画します。“Log Off”リンクをクリックすると、ユーザーはログアウトします。


図4

 上記のログイン、ログアウト、登録の機能は、プロジェクトが作成されたときにVisual Studioがプロジェクトに追加したAccountControllerクラスで実装されています。AccountControllerのUIは\Views\Accountディレクトリのビュー・テンプレートを使用して実装されています。


図5

 AccountControllerクラスは、ASP.NETのフォーム認証を使用して暗号化された認証クッキーを発行し、ASP.NETメンバーシップAPIを使用してユーザー名/パスワードの保存および検証を行います。ASP.NETメンバーシップAPIは拡張可能で、どのようなパスワード認証ストアでも利用できます。ASP.NETはビルトインのメンバーシップ・プロバイダ実装とともに出荷されていて、SQL ServerデータベースまたはActive Directory内にユーザー名/パスワードを保存します。

 どのメンバーシップ・プロバイダをNerdDinnerで使用するかは、プロジェクトのルートにある“web.config”ファイルを開いて、その中の<membership>セクションで構成できます。プロジェクトが作成されたときに追加されるデフォルトのweb.configは、SQLメンバーシップ・プロバイダ(SqlMembershipProvider)を登録し、データベースのロケーションの指定に“ApplicationServices”という名前の接続文字列を使用して、それを構成します。

 デフォルトの“ApplicationServices”接続文字列(web.configファイルの<connectionStrings>で指定)は、SQL Server Expressを使用するように構成されています。それは、アプリケーションの“App_Data”ディレクトリ配下にある“ASPNETDB.MDF”という名前のSQL Server Expressデータベースを指しています。アプリケーションで初めてメンバーシップAPIが使用されて、このデータベースが存在しない場合、ASP.NETは自動的にデータベースを作成し、そこで適切なメンバーシップのデータベース・スキーマを準備します。


図6

 SQL Server Expressの代わりに、完全なSQL Serverのインスタンス(またはリモート・サーバへの接続)を使用したい場合、必要なことはweb.configファイルの“ApplicationServices”接続文字列を更新して、それが指しているデータベースに適切なメンバーシップのスキーマが追加されているかどうかを確認するだけです。\Windows\Microsoft.NET\Framework\v2.0.50727\ディレクトリにある“aspnet_regsql.exe”ユーティリティを実行して、メンバーシップの適切なスキーマや、そのほかのASP.NETアプリケーション・サービスをデータベースに追加できます。

[Authorize]フィルタを使用して/Dinners/Create URLを許可

 NerdDinnerアプリケーションで安全な認証やアカウント管理の実装を可能にするために、コードは何も書いていません。ユーザーはアプリケーションで新しいアカウントの登録やサイトのログイン/ログアウトができます。

 ここからは、アプリケーションに承認ロジックを追加し、サイト内で何ができて何ができないかを制御するために、訪問者の認証状態やユーザー名を使用できます。まずDinnersControllerクラスの“Create”アクション・メソッドに承認ロジックを追加しましょう。具体的には、/Dinners/Create URLにアクセスするユーザーは、ログインしていなければならないようにします。ログインしていない場合、ログイン・ページに移動させて、ログインできるようにします。

 このロジックの実装は非常に簡単です。以下のように、[Authorize]フィルタ属性をCreateアクション・メソッドに追加するだけです。

//
// GET: /Dinners/Create

[Authorize]
public ActionResult Create() {
   ...
}

//
// POST: /Dinners/Create

[AcceptVerbs(HttpVerbs.Post), Authorize]
public ActionResult Create(Dinner dinnerToCreate) {
   ...
}

 ASP.NET MVCでは“アクション・フィルタ”を作成でき、これはアクション・メソッドに宣言的に適用できる再利用可能なロジックを実装するために使用されます。[Authorize]フィルタは、ASP.NET MVCが提供しているビルトインのアクション・フィルタの1つで、これにより開発者はアクション・メソッドやコントローラ・クラスに宣言的に承認ルールを適用できます。

 (上記のように)パラメータなしで適用すると、[Authorize]フィルタはそのアクション・メソッドを要求したユーザーにログインを強制し、もしログインURLでなければ自動的にブラウザをリダイレクトします。このリダイレクトが行われたとき、最初にリクエストしていたURLはクエリ文字列の引数として引き渡されます(例えば、/Account/LogOn ReturnUrl=%2fDinners%2fCreate)。そしてAccountControllerは、そのユーザーがログインすると、最初にリクエストしていたURLへリダイレクトします。

 [Authorize]フィルタはオプションで“Users”や“Roles”プロパティを指定する機能をサポートしており、それはそのユーザーがログイン済みであり、かつ許可されたユーザーの一覧や許可されたセキュリティ・ロールのメンバーであることが必要な場合に使用できます。例えば以下のコードは、2人の特定のユーザー、“scottgu”および“billg”だけが、/Dinners/Create URLにアクセスできます。


[Authorize(Users="scottgu,billg")]
public ActionResult Create() {
  ...
}

 コード内に特定のユーザー名を埋め込むと、非常に保守が難しくなります。よりよい方法は、高いレベルの“ロール”を定義することです。これはコードによりチェックされ、データベースまたはActive Directoryシステムのどちらかを使用して、そのロールにユーザーをマッピングします(実際のユーザー・マッピング・リストをコードの外部に保存できるようにします)。ASP.NETにはビルトインのロール管理APIとロール・プロバイダ一式が含まれており(SQL ServerおよびActive Directory用のものを含む)、これがユーザー/ロールのマッピングの実行をサポートします。そのため、コードを修正して、特に“admin”ロールのユーザーのみが/Dinners/Create URLへアクセスできるようにすることが可能です。

[Authorize(Roles="admin")]
public ActionResult Create() {
   ...
}

夕食会の作成時にUser.Identity.Nameプロパティを使用

 コントローラの基本クラスに公開されているUser.Identity.Nameプロパティを使用して、リクエストしている現在のログイン・ユーザーのユーザー名を取得できます。

 以前にCreateアクション・メソッドのHTTP-POST版を実装したとき、夕食会の“HostedBy”プロパティを静的な文字列でハードコーディングしました。代わりにここでは、このコードがUser.Identity.Nameプロパティを使用し、その夕食会を作成したホストに対しては自動的にRSVPを追加するように更新できます。

//
// POST: /Dinners/Create

[AcceptVerbs(HttpVerbs.Post), Authorize]
public ActionResult Create(Dinner dinner) {

  if (ModelState.IsValid) {

    try {
      dinner.HostedBy = User.Identity.Name;

      RSVP rsvp = new RSVP();
      rsvp.AttendeeName = User.Identity.Name;
      dinner.RSVPs.Add(rsvp);

      dinnerRepository.Add(dinner);
      dinnerRepository.Save();

      return RedirectToAction("Details", new { id=dinner.DinnerID });
    }
    catch {
      ModelState.AddModelErrors(dinner.GetRuleViolations());
    }
  }

  return View(new DinnerFormViewModel(dinner));
}

 [Authorize]属性をCreateメソッドに追加したので、ASP.NET MVCは、/Dinners/Create URLを訪れたユーザーがサイトにログインしていれば、そのアクション・メソッドが実行されるようにします。そうすれば、User.Identity.Nameプロパティの値は常に有効なユーザー名になります。

夕食会の編集時にUser.Identity.Nameプロパティを使用

 それでは、ユーザーを制限する承認ロジックをいくつか追加してみましょう。それにより、ユーザーはホストしている夕食会のプロパティだけを編集可能になります。

 これをサポートするのに、まず(先に構築したDinner.csの部分クラス内の)Dinnerオブジェクトに“IsHostedBy(username)”ヘルパー・メソッドを追加します。このヘルパー・メソッドは、指定されたユーザー名がその夕食会のHostedByプロパティに合致しているかどうかの真偽を返し、大文字小文字の区別なくそれらの文字列比較を実行するのに必要なロジックをカプセル化します。

public partial class Dinner {

  public bool IsHostedBy(string userName) {
    return HostedBy.Equals(userName, StringComparison.InvariantCultureIgnoreCase);
  }
}

 そして、DinnersControllerクラス内のEditアクション・メソッドに[Authorize]属性を追加します。これにより、ユーザーは/Dinners/Edit/[id] URLをリクエストするときは、ログインしなければならなくなります。

 その後、Dinner.IsHostedBy(username)ヘルパー・メソッドを使用して、その夕食会のホストとログイン・ユーザーが合致しているかを検証するためのEditメソッドにコードを追加できます。もしそのユーザーがホストでなければ、“InvalidOwner”ビューを表示して、そのリクエストを終了させます。これを行うコードは以下のようになります。

//
// GET: /Dinners/Edit/5

[Authorize]
public ActionResult Edit(int id) {

  Dinner dinner = dinnerRepository.GetDinner(id);

  if (!dinner.IsHostedBy(User.Identity.Name))
    return View("InvalidOwner");

  return View(new DinnerFormViewModel(dinner));
}

//
// POST: /Dinners/Edit/5

[AcceptVerbs(HttpVerbs.Post), Authorize]
public ActionResult Edit(int id, FormCollection collection) {

  Dinner dinner = dinnerRepository.GetDinner(id);

  if (!dinner.IsHostedBy(User.Identity.Name))
    return View("InvalidOwner");

  try {
    UpdateModel(dinner);

    dinnerRepository.Save();

    return RedirectToAction("Details", new {id = dinner.DinnerID});
  }
  catch {
    ModelState.AddModelErrors(dinnerToEdit.GetRuleViolations());

    return View(new DinnerFormViewModel(dinner));
  }
}

 その後、新しい“InvalidOwner”ビューを作成するために、\Views\Dinnersディレクトリ上で右クリックして、[Add]−[View]メニュー・コマンドを選択します。そしてそれを以下のエラー・メッセージとひも付けます。

<asp:Content ID="Title" ContentPlaceHolderID="TitleContent" runat="server">
  You Don't Own This Dinner
</asp:Content>

<asp:Content ID="Main" ContentPlaceHolderID="MainContent" runat="server">

  <h2>Error Accessing Dinner</h2>

  <p>Sorry - but only the host of a Dinner can edit or delete it.</p>

</asp:Content>

 これからは、ユーザーが所有していない夕食会を編集しようとすると、エラー・メッセージが表示されます。


図7

 コントローラ内のDeleteアクション・メソッドにも同じ手順を繰り返し、夕食会の削除権をロックダウンして、夕食会のホストだけがそれを削除できるようできます。

EditとDeleteリンクの表示/非表示

 Details URLからは、DinnersControllerクラスのEditとDeleteアクション・メソッドへリンクしています(図8)。


図8

 現在、EditおよびDeleteアクションのリンクは、そのDetails URLへの訪問者が夕食会のホストかどうかにかかわらず表示しています。その訪問ユーザーがその夕食会の所有者である場合のみ、そのリンクを表示するように変更しましょう。

 DinnersController内のDetailsアクション・メソッドは、Dinnerオブジェクトを取得してから、それをビュー・テンプレートへモデル・オブジェクトとして引き渡しています。

//
// GET: /Dinners/Details/5

public ActionResult Details(int id) {

  Dinner dinner = dinnerRepository.GetDinner(id);

  if (dinner == null)
    return View("NotFound");

  return View(dinner);
}

 以下のようにDinner.IsHostedByヘルパー・メソッド使用することで、EditおよびDeleteリンクを条件に応じて表示/非表示するようにビュー・テンプレートを更新できます。

<% if (Model.IsHostedBy(Context.User.Identity.Name)) { %>

   <%= Html.ActionLink("Edit Dinner", "Edit", new { id=Model.DinnerID }) %> |
   <%= Html.ActionLink("Delete Dinner", "Delete", new {id=Model.DinnerID}) %>

<% } %>

次のステップ

 それでは認証済みユーザーがAJAXを使用した夕食会のRSVPを可能にする方法を見てみましょう。

[注:NerdDinnerアプリケーションの完成版はhttp://nerddinner.codeplex.com/からダウンロードできます。] End of Article

 
インデックス・ページヘ  「NerdDinnerチュートリアル」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間