web-dev-qa-db-ja.com

ASP.NET MVCでの不正なコントローラーのリダイレクト

管理者ロールに制限しているASP.NET MVCのコントローラーがあります。

[Authorize(Roles = "Admin")]
public class TestController : Controller
{
   ...

管理者ロールに属していないユーザーがこのコントローラーに移動すると、空白の画面が表示されます。

私がやりたいのは、「このリソースにアクセスするには管理者の役割である必要がある」というビューにリダイレクトすることです。

私が考えたこれを行う1つの方法は、IsUserInRole()の各アクションメソッドをチェックし、役割がない場合はこの情報ビューを返すことです。ただし、DRYプリンシパルを壊し、維持するのが明らかに面倒な各アクションにそれを配置する必要があります。

73
Guy

AuthorizeAttributeに基づいてカスタム認証属性を作成し、OnAuthorizationをオーバーライドして、目的のチェックを実行します。通常、AuthorizeAttributeは、認証チェックが失敗した場合、フィルター結果をHttpUnauthorizedResultに設定します。代わりに、(エラービューの)ViewResultに設定することもできます。

[〜#〜] edit [〜#〜]:ブログの投稿がいくつかあります。

例:

    [AttributeUsage( AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false )]
    public class MasterEventAuthorizationAttribute : AuthorizeAttribute
    {
        /// <summary>
        /// The name of the master page or view to use when rendering the view on authorization failure.  Default
        /// is null, indicating to use the master page of the specified view.
        /// </summary>
        public virtual string MasterName { get; set; }

        /// <summary>
        /// The name of the view to render on authorization failure.  Default is "Error".
        /// </summary>
        public virtual string ViewName { get; set; }

        public MasterEventAuthorizationAttribute()
            : base()
        {
            this.ViewName = "Error";
        }

        protected void CacheValidateHandler( HttpContext context, object data, ref HttpValidationStatus validationStatus )
        {
            validationStatus = OnCacheAuthorization( new HttpContextWrapper( context ) );
        }

        public override void OnAuthorization( AuthorizationContext filterContext )
        {
            if (filterContext == null)
            {
                throw new ArgumentNullException( "filterContext" );
            }

            if (AuthorizeCore( filterContext.HttpContext ))
            {
                SetCachePolicy( filterContext );
            }
            else if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
            {
                // auth failed, redirect to login page
                filterContext.Result = new HttpUnauthorizedResult();
            }
            else if (filterContext.HttpContext.User.IsInRole( "SuperUser" ))
            {
                // is authenticated and is in the SuperUser role
                SetCachePolicy( filterContext );
            }
            else
            {
                ViewDataDictionary viewData = new ViewDataDictionary();
                viewData.Add( "Message", "You do not have sufficient privileges for this operation." );
                filterContext.Result = new ViewResult { MasterName = this.MasterName, ViewName = this.ViewName, ViewData = viewData };
            }

        }

        protected void SetCachePolicy( AuthorizationContext filterContext )
        {
            // ** IMPORTANT **
            // Since we're performing authorization at the action level, the authorization code runs
            // after the output caching module. In the worst case this could allow an authorized user
            // to cause the page to be cached, then an unauthorized user would later be served the
            // cached page. We work around this by telling proxies not to cache the sensitive page,
            // then we hook our custom authorization code into the caching mechanism so that we have
            // the final say on whether a page should be served from the cache.
            HttpCachePolicyBase cachePolicy = filterContext.HttpContext.Response.Cache;
            cachePolicy.SetProxyMaxAge( new TimeSpan( 0 ) );
            cachePolicy.AddValidationCallback( CacheValidateHandler, null /* data */);
        }


    }
69
tvanfosson

カスタムHandleUnauthorizedRequest内でオーバーライド可能 AuthorizeAttribute を使用できます。

このような:

protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
    // Returns HTTP 401 by default - see HttpUnauthorizedResult.cs.
    filterContext.Result = new RedirectToRouteResult(
    new RouteValueDictionary 
    {
        { "action", "YourActionName" },
        { "controller", "YourControllerName" },
        { "parameterName", "YourParameterValue" }
    });
}

次のようなこともできます:

private class RedirectController : Controller
{
    public ActionResult RedirectToSomewhere()
    {
        return RedirectToAction("Action", "Controller");
    }
}

これでHandleUnauthorizedRequestメソッドで次のように使用できます:

filterContext.Result = (new RedirectController()).RedirectToSomewhere();
25

「tvanfosson」によるコードは、「子リクエストの実行エラー」を与えていました。OnAuthorizationを次のように変更しました。

public override void OnAuthorization(AuthorizationContext filterContext)
    {
        base.OnAuthorization(filterContext);

        if (!_isAuthorized)
        {
            filterContext.Result = new HttpUnauthorizedResult();
        }
        else if (filterContext.HttpContext.User.IsInRole("Administrator") || filterContext.HttpContext.User.IsInRole("User") ||  filterContext.HttpContext.User.IsInRole("Manager"))
        {
            // is authenticated and is in one of the roles 
            SetCachePolicy(filterContext);
        }
        else
        {
            filterContext.Controller.TempData.Add("RedirectReason", "You are not authorized to access this page.");
            filterContext.Result = new RedirectResult("~/Error");
        }
    }

これはうまく機能し、エラーページにTempDataを表示します。コードスニペットの「tvanfosson」に感謝します。私はWindows認証を使用していますが、_isAuthorizedはHttpContext.User.Identity.IsAuthenticatedにすぎません...

9
sajoshi

同じ問題がありました。 MVCコードを把握するのではなく、機能するように見える安価なハックを選択しました。 Global.asaxクラスの場合:

member x.Application_EndRequest() =
  if x.Response.StatusCode = 401 then 
      let redir = "?redirectUrl=" + Uri.EscapeDataString x.Request.Url.PathAndQuery
      if x.Request.Url.LocalPath.ToLowerInvariant().Contains("admin") then
          x.Response.Redirect("/Login/Admin/" + redir)
      else
          x.Response.Redirect("/Login/Login/" + redir)
5
MichaelGG

この問題はここ数日私を悩ませてきたので、上記のtvanfossonの答えと肯定的に機能する答えを見つける際に、答えの中核部分を強調し、いくつかの関連するキャッチャーに対処する価値があると思いました。

核となる答えは、これ、甘くてシンプルです:

filterContext.Result = new HttpUnauthorizedResult();

私の場合、ベースコントローラーから継承するため、それを継承する各コントローラーでOnAuthorizeをオーバーライドします。

protected override void OnAuthorization(AuthorizationContext filterContext)
{
    base.OnAuthorization(filterContext);
    YourAuth(filterContext); // do your own authorization logic here
}

問題は、「YourAuth」で、機能するだけでなく、リクエストをすぐに終了するだろうと思った2つのことを試したことです。まあ、それはそれがどのように機能するかではありません。そのため、まず、予期しない2つの機能が機能しません。

filterContext.RequestContext.HttpContext.Response.Redirect("/Login"); // doesn't work!
FormsAuthentication.RedirectToLoginPage(); // doesn't work!

それらが機能しないだけでなく、リクエストも終了しません。つまり、次のことを意味します。

if (!success) {
    filterContext.Result = new HttpUnauthorizedResult();
}
DoMoreStuffNowThatYouThinkYourAuthorized();

さて、上記の正解であっても、ロジックのフローはまだ継続しています! OnAuthorize内でDoMoreStuff ...をヒットします。したがって、それを念頭に置いてください(したがって、DoMore ...は別の場所にある必要があります)。

しかし、正しい答えでは、OnAuthorizeロジックのフローは最後まで続きますが、その後は本当に期待どおりの結果が得られます(ログインページへのリダイレクト(webconfigのForms authで設定されている場合))。

しかし、予期せず、1)Response.Redirect( "/ Login")が機能しません。Actionメソッドが引き続き呼び出され、2)FormsAuthentication.RedirectToLoginPage();同じことを行います:Actionメソッドが呼び出されます!

私にとって、特に後者についてはまったく間違っているように見えます:FormsAuthentication.RedirectToLoginPageがリクエストを終了しない、またはfilterContext.Result = new HttpUnauthorizedResult()が行うことと同等のことをするだろうと誰が思ったでしょうか?

2

これをコメントとして残しておく必要がありますが、私はもっと多くの担当者が必要です、とにかくNicholas Petersonに、おそらく応答を終了するように指示するために2番目の引数をリダイレクト呼び出しに渡すことを伝えたいだけです。これを処理する最も優雅な方法ではありませんが、実際には機能します。

そう

filterContext.RequestContext.HttpContext.Response.Redirect("/Login", true);

の代わりに

filterContext.RequestContext.HttpContext.Response.Redirect("/Login);

あなたのコントローラーにこれがあるでしょう:

 protected override void OnAuthorization(AuthorizationContext filterContext)
 {
      if(!User.IsInRole("Admin")
      {
          base.OnAuthorization(filterContext);
          filterContext.RequestContext.HttpContext.Response.Redirect("/Login", true);
      }
 }
1
Lazy Coder

Windows認証( 前のトピック )を使用して開発サーバーでVisual Studioから実行すると、おそらく空白のページが表示されます。

IISにデプロイする場合、特定のステータスコード(この場合は401)のカスタムエラーページを構成できます。system.webServerにhttpErrorsを追加します。

<httpErrors>
  <remove statusCode="401" />
  <error statusCode="401" path="/yourapp/error/unauthorized" responseMode="Redirect" />
</httpErrors>

次に、ErrorController.Unauthorizedメソッドと対応するカスタムビューを作成します。

1
Mark Meyerovich

独自のAuthorize-filter属性を作成する必要があります。

ここに私が勉強します;)

Public Class RequiresRoleAttribute : Inherits ActionFilterAttribute
    Private _role As String

    Public Property Role() As String
        Get
            Return Me._role
        End Get
        Set(ByVal value As String)
            Me._role = value
        End Set
    End Property

    Public Overrides Sub OnActionExecuting(ByVal filterContext As System.Web.Mvc.ActionExecutingContext)
        If Not String.IsNullOrEmpty(Me.Role) Then
            If Not filterContext.HttpContext.User.Identity.IsAuthenticated Then
                Dim redirectOnSuccess As String = filterContext.HttpContext.Request.Url.AbsolutePath
                Dim redirectUrl As String = String.Format("?ReturnUrl={0}", redirectOnSuccess)
                Dim loginUrl As String = FormsAuthentication.LoginUrl + redirectUrl

                filterContext.HttpContext.Response.Redirect(loginUrl, True)
            Else
                Dim hasAccess As Boolean = filterContext.HttpContext.User.IsInRole(Me.Role)
                If Not hasAccess Then
                    Throw New UnauthorizedAccessException("You don't have access to this page. Only " & Me.Role & " can view this page.")
                End If
            End If
        Else
            Throw New InvalidOperationException("No Role Specified")
        End If

    End Sub
End Class
1
Ropstah