認証と承認を利用した安全なアプリケーション

提供元: Microsoft

PDF のダウンロード

これは、ASP.NET MVC 1 を使用して小規模で完全な Web アプリケーションを構築する方法を説明する無料の "NerdDinner" アプリケーション チュートリアルの手順 9 です。

手順 9 では、NerdDinner アプリケーションを保護するための認証と承認を追加する方法を示します。そうすると、ユーザーはサイトに登録してログインして新しいディナーを作成するよう求められ、ディナーをホストしているユーザーのみが後で編集できるようになります。

ASP.NET MVC 3 を使用している場合は、MVC 3 の概要または MVC Music Store のチュートリアルに従うことをお勧めします。

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

現在、NerdDinner アプリケーションは、サイトにアクセスするすべてのユーザーに、ディナーの詳細を作成および編集する機能を付与しています。 これを変更して、ユーザーが新しいディナーを作成するには、サイトに登録し、ログインする必要があり、ディナーをホストしているユーザーのみが後で編集できるように制限を追加します。

これを有効にするには、認証と承認を使用してアプリケーションを保護します。

認証と承認について

認証 は、アプリケーションにアクセスするクライアントの ID を識別して検証するプロセスです。 簡単に言えば、エンドユーザーが Web サイトにアクセスしたときに"誰"であるかを特定することです。 ASP.NET では、ブラウザー ユーザーを認証する複数の方法がサポートされています。 インターネット Web アプリケーションの場合、最も一般的な認証方法は "フォーム認証" と呼ばれます。 フォーム認証を使用すると、開発者はアプリケーション内で HTML ログイン フォームを作成し、エンドユーザーがデータベースまたはその他のパスワード資格情報ストアに対して送信するユーザー名/パスワードを検証できます。 ユーザー名とパスワードの組み合わせが正しい場合、開発者は、今後の要求でユーザーを識別するために、ASP.NET によって暗号化された HTTP Cookie を発行することができます。 NerdDinner アプリケーションでは、フォーム認証を使用します。

承認 とは、認証されたユーザーが特定の URL/リソースにアクセスするためのアクセス許可を持っているか、何らかのアクションを実行するのかを判断するプロセスです。 たとえば、NerdDinner アプリケーション内では、ログインしているユーザーのみが /Dinners/Create URL にアクセス し、新しいディナーを作成できることを承認する必要があります。 また、ディナーをホストしているユーザーのみが編集できるように承認ロジックを追加し、他のすべてのユーザーに対して編集アクセスを拒否する必要があります。

フォーム認証と AccountController

ASP.NET MVC の既定の Visual Studio プロジェクト テンプレートでは、新しい ASP.NET MVC アプリケーションが作成されたときにフォーム認証が自動的に有効になります。 また、事前に構築されたアカウント ログイン ページの実装がプロジェクトに自動的に追加されるため、セキュリティをサイト内に簡単に統合できます。

サイトにアクセスするユーザーが認証されていない場合、サイトの右上には、既定の Site.master マスター ページによって [ログオン] リンクが表示されます。

Screenshot of the Nerd Dinner Host a Dinner page. Log On is highlighted in the top right corner.

[ログオン] リンクをクリックすると、ユーザーは /Account/LogOn URL に 移動します。

Screenshot of the Nerd Dinner Log On page.

訪問者が登録済みではない場合は、[登録] リンクをクリックすると、/Account/Register URL に 移動し、アカウントの詳細を入力できるようになります。

Screenshot of the Nerd Dinner Create a New Account page.

[登録] ボタンをクリックすると、ASP.NET メンバーシップ システム内に新しいユーザーが作成され、ユーザーはフォーム認証によってサイトに対して認証されます。

ユーザーがログインすると、Site.master はページの右上を変更して "ようこそ [username]!" メッセージを出力し、"ログオン" リンクではなく "ログオフ" リンクを表示するようになります。 [ログオフ] リンクをクリックすると、ユーザーがログアウトします。

Screenshot of the Nerd Dinner Host a Dinner form page. The Welcome and Log Off buttons are highlighted in the top right corner.

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

Screenshot of the Nerd Dinner navigation tree. Account Controller dot c s is highlighted. The Account Folder and menu items are also highlighted.

AccountController クラスは、ASP.NET フォーム認証システムを使用して暗号化された認証 Cookie を発行し、ASP.NET Membership API を使用してユーザー名/パスワードを格納および検証します。 ASP.NET Membership API は拡張可能であり、あらゆるパスワード資格情報ストアを使用できます。 ASP.NET には、SQL データベース内または Active Directory 内にユーザー名とパスワードを格納するビルトイン メンバーシップ プロバイダー実装が付属しています。

プロジェクトのルートにある "web.config" ファイルを開き、その中の<メンバーシップ> セクションを探すことで、NerdDinner アプリケーションで使用するメンバーシップ プロバイダーを構成できます。 プロジェクトの作成時に追加された既定の web.config は、SQL メンバーシップ プロバイダーを登録し、"ApplicationServices" という名前の接続文字列を使用してデータベースの場所を指定するように構成します。

既定の "ApplicationServices" 接続文字列 (web.config ファイルの <connectionStrings> セクション内で指定) は、SQL Express を使用するように構成されます。 これは、アプリケーションの "App_Data" ディレクトリにある "ASPNETDB.MDF" というデータベースを参照します。 アプリケーション内で Membership API が初めて使用されるときにこのデータベースが存在しない場合、ASP.NET は自動的にデータベースを作成し、その中に適切なメンバーシップ データベース スキーマをプロビジョニングします。

Screenshot of the Nerd Dinner navigation tree. App Data is expanded and A S P NET D B dot M D F is selected.

SQL 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 では、アクション メソッドに宣言的に適用可能で、再利用可能なロジックを実装するために使用できる "アクション フィルター" を作成する機能がサポートされています。 [承認] フィルターは、ASP.NET MVC によって提供されるビルトイン アクション フィルターの 1 つであり、開発者はアクション メソッドとコントローラー クラスに承認規則を宣言的に適用できます。

上記のようなパラメーターを指定せずに適用すると、[Authorize] フィルターは強制的に、アクション メソッド要求を行うユーザーにログインを要求します。そうでない場合は、ブラウザーでログイン URL に自動的にリダイレクトされます。 このリダイレクトを行うと、最初に要求された URL が querystring 引数として渡されます (例: /Account/LogOn?ReturnUrl=%2fDinners%2fCreate)。 その後、AccountController は、ユーザーがログインすると、最初に要求された URL にリダイレクトを行います。

[承認] フィルターは、必要に応じて、ユーザーがログインしていることを要求するために使用できる "Users" または "Roles" プロパティを指定する機能をサポートします。このプロパティは、ユーザーがログイン状態であり、なおかつ許可されたセキュリティ ロールの許可ユーザーまたはメンバーのリストに存在することを要求するために使用できます。 たとえば、次のコードでは、"scottgu" と "billg" の 2 人の特定のユーザーのみが /Dinners/Create URL にアクセスできます。

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

ただし、コード内に特定のユーザー名を埋め込むと、保守することが難しくなるでしょう。 より良い方法は、コードがチェックする上位レベルの "ロール" を定義し、データベースまたは Active Directory システムを使用して一連のユーザーをロールにマップすることです (実際のユーザー マッピング リストを、コードによって外部から格納できるようにします)。 ASP.NET には、ビルトイン ロール管理 API と、このユーザー/ロール マッピングの実行に役立つビルトイン ロール プロバイダー (SQL および Active Directory 用のプロバイダーを含む) が含まれています。 その後、特定の "管理者" ロール内のユーザーのみが /Dinners/Create URL にアクセスできるようにコードを更新できます。

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

ディナーの作成時に User.Identity.Name プロパティを使用する

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

上記で、Create() アクション メソッドの HTTP-POST バージョンを実装したときには、Dinner の "HostedBy" プロパティを静的文字列にハードコーディングしていました。 現在は、User.Identity.Name プロパティを使用するようにこのコードを更新し、さらに、Dinner を作成するホストの 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));
}

Create() メソッドに [Authorize] 属性を追加したため、ASP.NET MVC では、/Dinners/Create URL にアクセスするユーザーがサイトにログイン済みである場合にのみアクション メソッドが実行されます。 そのため、User.Identity.Name プロパティの値には常に有効なユーザー名が含まれます。

ディナーの編集時に User.Identity.Name プロパティを使用する

ここで、自分がホストしているディナーのプロパティのみを編集できるように、ユーザーを制限する承認ロジックをいくつか追加してみましょう。

これを支援するために、まず Dinner オブジェクトに "IsHostedBy(username)" ヘルパー メソッドを追加します (前にビルドしたDinner.cs部分クラス内で行います)。 このヘルパー メソッドは、指定されたユーザー名が Dinner HostedBy プロパティと一致するかどうかに応じて true または false を返し、大文字と小文字を区別しない文字列比較を実行するために必要なロジックをカプセル化します。

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) ヘルパー メソッドを使用して、ログインしているユーザーが Dinner ホストと一致することを確認する 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));
    }
}

その後、\Views\Dinners ディレクトリで右クリックし、[> ビューの追加] メニュー コマンドを選択すると、新しい "InvalidOwner" ビューを作成できます。 ここには、次のエラー メッセージを記入します。

<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>

ユーザーが所有していないディナーを編集しようとすると、エラー メッセージが表示されます。

Screenshot of the Error Message on the Nerd Dinner web page.

コントローラー内の Delete() アクション メソッドについても同じ手順を繰り返して、ディナーを削除するアクセス許可をロックダウンし、ディナーのホストのみが削除できるようにします。

DinnersController クラスの [Edit] および [Delete] メソッドには、Details URL からリンクさせています。

Screenshot of the Nerd Dinner page. The Edit and Delete buttons are circled at the bottom. The details U R L is circled at the top.

現在、詳細 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() ヘルパー メソッドを使用して、ビュー テンプレートを更新すると、条件によって [編集] リンクと [削除] リンクを表示/非表示にすることができます。

<% 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 を使用して、ディナーに対して応答を要求できるようにする方法を見てみましょう。