AuthorizeAttributeが認証と承認の失敗のためにログインページにリダイレクトするのはなぜですか?


265

ASP.NET MVCでは、次のAuthorizeAttributeようにコントローラーメソッドをでマークアップできます。

[Authorize(Roles = "CanDeleteTags")]
public void Delete(string tagName)
{
    // ...
}

つまり、現在ログインしているユーザーが "CanDeleteTags"ロールに属していない場合、コントローラーメソッドは呼び出されません。

残念ながら、失敗した場合AuthorizeAttributeは、HttpUnauthorizedResultを返します。これは常にHTTPステータスコード401を返します。これにより、ログインページへのリダイレクトが発生します。

ユーザーがログインしていない場合、これは完全に理にかなっています。ただし、ユーザーがすでにログインしているが、必要なロールに属していないログインページに戻すのは混乱します。

のようだ AuthorizeAttribute認証と認可統合する。

これはASP.NET MVCの見落としのようですが、何か不足していますか?

DemandRoleAttribute2つを分離するを調理しなければなりませんでした。ユーザーが認証されない場合、HTTP 401が返され、ログインページに送信されます。ユーザーがログインしているが、必要な役割ではない場合は、NotAuthorizedResult代わりにを作成します。現在、これはエラーページにリダイレクトされます。

確かに私はこれをする必要はなかったのですか?


10
すばらしい質問であり、同意します。HTTPNot Authorizedステータスがスローされるはずです。
Pure.Krome

3
私はあなたの解決策が好きです、ロジャー。そうでない場合でも。
Jon Davis

ログインページには、ユーザーがすでに認証を受けている場合、ユーザーを単にReturnUrlにリダイレクトするためのチェックがあります。したがって、302リダイレクトの無限ループを作成することができました:D woot。
juhan_h

1
チェックアウト これを
ジョギ

ロジャー、あなたのソリューションに関する良い記事-red-gate.com/simple-talk/dotnet/asp-net/…あなたのソリューションがこれをきれいに行う唯一の方法のようです
Craig

回答:


305

それが最初に開発されたとき、System.Web.Mvc.AuthorizeAttributeは正しいことをしていました-HTTP仕様の古いリビジョンは、「無許可」と「非認証」の両方にステータスコード401を使用していました。

元の仕様から:

要求にすでに認証資格情報が含まれている場合、401応答は、それらの資格情報の認証が拒否されたことを示します。

実際、混乱がすぐにわかります。「認証」を意味する「認可」という言葉を使用しています。ただし、日常的には、ユーザーが認証されているが許可されていない場合は、403 Forbiddenを返す方が理にかなっています。ユーザーがアクセス権を与える2番目の資格情報セットを持つことはまずありません。

ほとんどのオペレーティングシステムを検討してください。アクセス権のないファイルを読み取ろうとすると、ログイン画面が表示されません。

ありがたいことに、あいまいさを取り除くためにHTTP仕様が更新されました(2014年6月)。

「ハイパーテキストトランスポートプロトコル(HTTP / 1.1):認証」(RFC 7235)から:

401(無許可)ステータスコードは、ターゲットリソースの有効な認証資格情報がないため、要求が適用されなかったことを示します。

「ハイパーテキスト転送プロトコル(HTTP / 1.1):セマンティクスとコンテンツ」(RFC 7231)から:

403(禁止)ステータスコードは、サーバーがリクエストを理解したが、承認を拒否したことを示します。

興味深いことに、ASP.NET MVC 1がリリースされた時点では、AuthorizeAttributeの動作は適切でした。現在、動作は正しくありません-HTTP / 1.1仕様が修正されました。

ASP.NETのログインページのリダイレクトを変更するのではなく、ソースで問題を修正するだけの方が簡単です。Webサイトのデフォルトの名前空間に同じ名前(AuthorizeAttribute)の新しい属性を作成できます(これは非常に重要です)。コンパイラーは、MVCの標準属性の代わりに自動的にそれを取得します。もちろん、そのアプローチを採用したい場合は、常に属性に新しい名前を付けることができます。

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class AuthorizeAttribute : System.Web.Mvc.AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(System.Web.Mvc.AuthorizationContext filterContext)
    {
        if (filterContext.HttpContext.Request.IsAuthenticated)
        {
            filterContext.Result = new System.Web.Mvc.HttpStatusCodeResult((int)System.Net.HttpStatusCode.Forbidden);
        }
        else
        {
            base.HandleUnauthorizedRequest(filterContext);
        }
    }
}

52
+1非常に良いアプローチ。小さな提案:代わりにチェックするのfilterContext.HttpContext.User.Identity.IsAuthenticated、あなただけ確認することができますfilterContext.HttpContext.Request.IsAuthenticated内蔵のヌルチェックが付属しており、参照してください。stackoverflow.com/questions/1379566/...
ダニエル・リウッツィ

> Webサイトのデフォルトの名前空間に同じ名前(AuthorizeAttribute)の新しい属性を作成すると、コンパイラーはMVCの標準の名前空間の代わりにそれを自動的に取得します。これによりエラーが発生します。型または名前空間 'Authorize'が見つかりませんでした(ディレクティブまたはアセンブリ参照がありませんか?)どちらもSystem.Web.Mvcを使用しています。そして、カスタムAuthorizeAttributeクラスの名前空間がコントローラーで参照されます。これを解決するには、[MyNamepace.Authorize]を使用する必要がありました
stormwild

2
@DePeter仕様ではリダイレクトについて何も述べられていないので、なぜリダイレクトがより良いソリューションなのですか?これだけで、ハックを行わずにajaxリクエストを殺すことができます。
Adam Tuliper-MSFT 2012年

1
これは明らかに動作上のバグであるため、MS Connectにログオンする必要があります。ありがとう。
Tony Wall

ところで、なぜログインページにリダイレクトされるのですか?同じリクエスト内で401コードとログインページを直接出力しないのはなぜですか?
SandRock、2014年

25

これをLogin Page_Load関数に追加します。

// User was redirected here because of authorization section
if (User.Identity != null && User.Identity.IsAuthenticated)
    Response.Redirect("Unauthorized.aspx");

ユーザーがそこにリダイレクトされたが、すでにログインしている場合は、不正なページが表示されます。ログインしていない場合は、失敗してログインページが表示されます。


18
Page_LoadはWebフォームのモジョです
チャンス

2
@Chance-次に、FormsAuthencationが呼び出されるように設定されている場所で呼び出されるコントローラーのデフォルトのActionMethodでそれを行います。
Pure.Krome

これは実際には非常にうまく機能しますが、MVCの場合if (User.Identity != null && User.Identity.IsAuthenticated) return RedirectToRoute("Unauthorized");Unauthorizedが定義されたルート名であるようなものになるはずです。
モーゼスマチュア

リソースに質問すると、ログインページにリダイレクトれ、再度 403ページにリダイレクトれますか?私には悪いようです。私は1つのリダイレクトをまったく許容できません。IMOこれはとにかくひどく構築されています。
SandRock、2014年

3
あなたの解決策によると、すでにログインしていて、URLを入力してログインページに移動すると、...許可されていないページに移動します。これは正しくありません。
Rajshekar Reddy

4

これは理にかなっているといつも思っていました。ログインしていて、自分が持っていないロールが必要なページにアクセスしようとすると、そのロールを持つユーザーでログインするように求めるログイン画面が表示されます。

ログインページに、ユーザーがすでに認証されているかどうかを確認するロジックを追加できます。彼らが再びそこに戻ってきた理由を説明するフレンドリーなメッセージを追加できます。


4
ほとんどの人は、特定のWebアプリに対して複数のIDを保持する傾向がないと感じています。もしそうなら、彼らは「私の現在のIDにはmojoがないので、もう1つとしてログインし直します」と考えるのに十分賢いです。
Roger Lipscombe

ログインページに何かを表示することについての他のポイントは良いものですが。ありがとう。
Roger Lipscombe

4

残念ながら、あなたはASP.NETフォーム認証のデフォルトの動作を扱っています。ここで説明されている回避策があります(私は試していません)。

http://www.codeproject.com/KB/aspnet/Custon401Page.aspx

(MVCに固有ではありません)

ほとんどの場合、最良の解決策は、ユーザーがアクセスしようとする前に、許可されていないリソースへのアクセスを制限することだと思います。許可されていないページに移動する可能性のあるリンクまたはボタンを削除/グレー表示する

権限のないユーザーをリダイレクトする場所を指定するために、属性に追加のパラメーターがあると便利です。しかし、その間、AuthorizeAttributeをセーフティネットと見なします。


承認に基づいてリンクを削除することも計画しているので(どこかで質問がありました)、後でHtmlHelper拡張メソッドをコーディングします。
Roger Lipscombe

1
ユーザーが直接URLにアクセスできないようにする必要があります。これがこの属性の目的です。カスタム401ソリューションにはあまり満足していません(少しグローバルのようです)。RedirectToRouteResultでNotAuthorizedResultをモデリングしてみます...
Roger Lipscombe

0

Global.ascxファイルのApplication_EndRequestハンドラでこれを試してください

if (HttpContext.Current.Response.Status.StartsWith("302") && HttpContext.Current.Request.Url.ToString().Contains("/<restricted_path>/"))
{
    HttpContext.Current.Response.ClearContent();
    Response.Redirect("~/AccessDenied.aspx");
}

0

aspnetcore 2.0を使用している場合は、これを使用してください:

using System;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;

namespace Core
{
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
    public class AuthorizeApiAttribute : Microsoft.AspNetCore.Authorization.AuthorizeAttribute, IAuthorizationFilter
    {
        public void OnAuthorization(AuthorizationFilterContext context)
        {
            var user = context.HttpContext.User;

            if (!user.Identity.IsAuthenticated)
            {
                context.Result = new UnauthorizedResult();
                return;
            }
        }
    }
}

0

私の場合の問題は、「HTTP仕様では、「未承認」と「非認証」の両方にステータスコード401が使用されていました」でした。ShadowChaserが言ったように。

この解決策は私にとってはうまくいきます:

if (User != null &&  User.Identity.IsAuthenticated && Response.StatusCode == 401)
{
    //Do whatever

    //In my case redirect to error page
    Response.RedirectToRoute("Default", new { controller = "Home", action = "ErrorUnauthorized" });
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.