ソースコードから理解する技術-UnderSourceCode

手を動かす(プログラムを組む)ことで技術を理解するブログ

ASP.NET MVC - フィルタでの認証後に任意の画面に遷移する

ASP.NET MVCには、コントローラやアクションに追加することで認証を行う
Authorizeフィルタが用意されています。

Authorizeフィルタを使用すると、認証されていないユーザーの場合はLogOn画面に遷移し
ログオンするとHome画面に遷移します。

認証が必要なページがある場合、フィルタにて認証を必須として、認証後は任意のページに
遷移させたいことが多いと思います。

以下、Authorizeフィルタの機能を拡張し、認証後に任意のページに遷移させるソースの説明です。
ASP.NET MVC 4 で実装しましたが、以前のバージョンのMVCでも使えると思います。

◆画面の流れ
1.Home画面
f:id:UnderSourceCode:20130504100353j:plain

右上に「Maintain」リンクを追加しました。
このリンクをクリック時に認証を行い、ログオンしていないユーザーの場合はLogOn画面をします。

2.LogOn画面
f:id:UnderSourceCode:20130504100410j:plain

テンプレートにて作成されるLogOn画面です。
Maintainリンクより遷移した場合、ログオン後にMaintain画面に遷移するようにします。

3.Maintain画面
f:id:UnderSourceCode:20130504100427j:plain
「Maintain」と言いながら、何の変哲もない画面です(苦笑)。
まあ、例ということで・・・。


ソースコード
1.HomeController


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MvcAuthentication.Util;

namespace MvcAuthentication.Controllers
{
public class HomeController : Controller
{
(中略)

[Authorize]
public ActionResult About()
{
ViewBag.Message = "Your quintessential app description page.";

return View();
}

(中略)

[CustomAuthorize(Roles = "Administrators")]
public ActionResult Maintain()
{
ViewBag.Message = "Site maintenance page.";

return View();
}
}
}


テンプレート作成時に作られるHomeControllers.csに、Maintainアクションを追加しました。
Home画面のリンククリック時に、このアクションが呼び出されます。
Maintainアクションには、今回独自に実装したCustomAuthorizeフィルタを追加しています。


2.CustomAuthorizeAttribute


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace MvcAuthentication.Util
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true,
AllowMultiple = true)]
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
filterContext.HttpContext.Session.Add("LogOnReturnUrl",
filterContext.HttpContext.Request.RawUrl);
base.OnAuthorization(filterContext);
}

protected override bool AuthorizeCore(HttpContextBase httpContext)
{
return base.AuthorizeCore(httpContext);
}
}
}


デフォルトのAuthorizeフィルタの実装であるAuthorizeAttributeクラスを継承し
CustomAuthorizeAttributeクラスを作成しました。

フィルタ、というよりは属性のクラス名は「属性名 + Attribute」となります。
今回のフィルタは「CustomAuthorize」なので、クラス名は「CustomAuthorizeAttribute」です。

AuthorizeAttributeクラス内の認証用メソッドであるOnAuthorization()をオーバーライドし
遷移元画面のURLをセッションにセットしています。

その後はデフォルトの動きと踏襲するため、AuthorizeAttributeクラスのOnAuthorization()を
呼び出しています。


3.AccountController


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Security;
using MvcAuthentication.Models;

namespace MvcAuthentication.Controllers
{

[Authorize]
public class AccountController : Controller
{

//
// GET: /Account/LogOn

[AllowAnonymous]
public ActionResult LogOn()
{
ViewBag.ReturnUrl = Session["LogOnReturnUrl"].ToString();
return ContextDependentView();
}

(中略)

//
// POST: /Account/LogOn

[AllowAnonymous]
[HttpPost]
public ActionResult LogOn(LogOnModel model, string returnUrl)
{
if (ModelState.IsValid)
{
if (Membership.ValidateUser(model.UserName, model.Password))
{
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
if (Url.IsLocalUrl(returnUrl))
{
if(Session["LogOnReturnUrl"].ToString() != string.Empty)
{
Session["LogOnReturnUrl"] = string.Empty;
}
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
}

// If we got this far, something failed, redisplay form
return View(model);
}

(中略)
}
}


テンプレート作成時に作られるAccountControllers.csに2箇所追加しました。

1つ目は、最初のLogOn()アクション内の
ViewBag.ReturnUrl = Session["LogOnReturnUrl"].ToString();
です。
セッションより取得した遷移先のURLをViewBagに設定します。

2つ目は、LogOn(LogOnModel model, string returnUrl) アクション内の

if(Session["LogOnReturnUrl"].ToString() != string.Empty)
{
Session["LogOnReturnUrl"] = string.Empty;
}

です。
returnUrlで指定されたURLに遷移する直前にセッション内のURLをクリアします。

これら以外はデフォルトのロジックで、returnUrlがある場合はそこに遷移するようになっています。
後はLogOn時に、returnUrlに遷移先のURLが渡るようにするだけです。


4.LogOn.cshtml


(中略)
@using (Html.BeginForm*1 {
<fieldset>
<legend>Log On Form</legend>
<ol>
<li>
@Html.LabelFor(m => m.UserName)
@Html.TextBoxFor(m => m.UserName)
@Html.ValidationMessageFor(m => m.UserName)
</li>
<li>
@Html.LabelFor(m => m.Password)
@Html.PasswordFor(m => m.Password)
@Html.ValidationMessageFor(m => m.Password)
</li>
<li>
@Html.CheckBoxFor(m => m.RememberMe)
@Html.LabelFor(m => m.RememberMe, new { @class = "checkbox" })
</li>
@Html.Hidden("returnUrl", (string)ViewBag.ReturnUrl)
</ol>
<input type="submit" value="Log On" title="Log On" />
</fieldset>
<p>
@Html.ActionLink("Register", "Register") if you don't have an account.
</p>
}

これもテンプレート作成時に作られるLogOn.cshtmlです。
遷移先のURLをLogOnアクションに渡すため、以下の記述でHiddenにURLをセットしています。

@Html.Hidden("returnUrl", (string)ViewBag.ReturnUrl)


以上です。
実際の開発ではログオン画面などは独自に作るとは思いますが、コントローラ内で
ログオン後の遷移先を指定したくない場合などに参考にしてください。

最後に、今回参考にしたサイトのURLを載せておきます。
ASP.NET MVC RedirectUrl on Login

*1:string)ViewBag.FormAction, "Account", FormMethod.Post