MVC4のRole Based Authenticationを利用して、ユーザーロールごとのメニューを表示する

参考URLは以下。

おおざっぱに書くと

  • Role Manager を有効にして
  • 必要であればCustome Role Providerなどを利用しつつユーザーにロールを設定し
  • Controller と View で Role に応じたルーティング、描画をする

という流れ。順番に書いていきます。

Role Managerを有効にする

Web.configのSystem.webセクションに roleManager セクションと membership セクションを追加します。Custom Role Providerを使う場合はここで指定しますが、今回はデフォルトの SimpleRoleProvider を利用することにしました。

<roleManager enabled="true" defaultProvider="SimpleRoleProvider">
  <providers>
    <clear/>
    <add name="SimpleRoleProvider" type="WebMatrix.WebData.SimpleRoleProvider, WebMatrix.WebData"/>
  </providers>
</roleManager>
<membership defaultProvider="SimpleMembershipProvider">
  <providers>
    <clear/>
    <add name="SimpleMembershipProvider" type="WebMatrix.WebData.SimpleMembershipProvider, WebMatrix.WebData" />
  </providers>
</membership>

この後の検証用にDatabase MigrationのSeedで適当にRoleを作ってユーザーに付与しておきます。Roleの作成や付与はSystem.Web.Security.Rolesクラスを利用しておこないます。

protected override void Seed(SNSAuthorization.Models.UsersContext context)
{
    WebSecurity.InitializeDatabaseConnection(
        "DefaultConnection",
        "UserProfile",
        "UserId",
        "UserName",
        autoCreateTables: true);

    if (!Roles.RoleExists("Administrator"))
    {
        Roles.CreateRole("Administrator");
    }

    if (!WebSecurity.UserExists("Admin"))
    {
        WebSecurity.CreateUserAndAccount("Admin", "admin");
    }

    if (!Roles.GetRolesForUser("Admin").Contains("Administrator"))
    {
        Roles.AddUserToRole("Admin", "Administrator");
    }
}

Pacakge manager consoleから以下のコマンドを実行することでデータを生成することができます。

PM> update-database

Custome Role Providerなどを利用しつつユーザーにロールを設定する

今回の主題とはちがうので割愛します。こちらを参照してください。

Controller と View で Role に応じたルーティング、描画をする

コントローラー側でログインしているユーザーのRoleに応じてルーティングを処理するにはRoles#GetRolesForUserなどが利用できます。

public ActionResult RedirectToDefault()
{
  String roles = Roles.GetRolesForUser();
  if (roles.Contains("Administrator"))
  {
    return RedirectToAction("About","Home");
  }
  else
  {
    return RedirectToAction("Index");
  }
}

また、リソースへのアクセス自体を制限するにはAuthorize Attributeが利用できます。

[Authorize(Role="Administrator")]
public class HomeController : Controller
{
  // ...
}

Viewでも同様にRoles#GetRolesForUserを使うことで描画する内容を変更することができます。

<ul id="menu">
@if (HttpContext.Current.User.Identity.IsAuthenticated)
{
    String roles = Roles.GetRolesForUser();
    var links = from item in menus
                where item.Roles.Split(new String { "," }, StringSplitOptions.RemoveEmptyEntries)
                .Any(x => roles.Contains(x) || x == "All")
                select item;
    foreach (var link in links)
    {
        @: <li> @Html.ActionLink(link.LinkText, link.ActionName,link.ControllerName)</li>
    }
}
else{
    var links = from item in menus
                where item.Roles.Split(new String{","},StringSplitOptions.RemoveEmptyEntries)
                .Any(x=>new String[]{"All","Anonymous"}.Contains(x))      
                select item;
     foreach ( var link in links){     
         @: <li> @Html.ActionLink(link.LinkText, link.ActionName, link.ControllerName)</li>         
     }
}
</ul> 

まとめ

Role Managerを利用することで割りと簡単にRoleごとの振る舞いを記述することができます。
素敵ですね :)

[追記]

上記のようにロールごとに表示するメニューを変更しようとすると、HomeControllerから操作するViewでWebSecurityクラスのメソッドを利用することになります。
このとき、何も設定を変更していないと以下のようなExceptionが投げられます。

"WebSecurity.InitializeDatabaseConnection" メソッドを呼び出してから、"WebSecurity" クラスのその他のメソッドを呼び出す必要があります。この呼び出しは、サイトのルートにある _AppStart.cshtml ファイル内に配置される必要があります。

どうもアプリを起動するごとにWebSecurity#InitializeDatabaseConnectionを使って、Membership が一度だけ初期化されるようにする必要があるようです。
今回の例のようにSimpleMembershipProviderを利用している場合は、単純にInitializeSimpleMembershipアトリビュートを付ければOKです。

[InitializeSimpleMembership]
public class HomeController : Controller
{
  // ...
}

コード本体はFilters/InitializeSimpleMembershipAttribute.csになります。向き先のDBを変更するときなどはここをいじることになりそうです。