ASP.NET MVCでは、次のようにAuthorizeAttribute
を使用してコントローラーメソッドをマークアップできます。
[Authorize(Roles = "CanDeleteTags")]
public void Delete(string tagName)
{
// ...
}
つまり、現在ログインしているユーザーが「CanDeleteTags」ロールに属していない場合、コントローラーメソッドは呼び出されません。
残念ながら、失敗の場合、AuthorizeAttribute
はHttpUnauthorizedResult
を返し、常にHTTPステータスコード401を返します。これにより、ログインページへのリダイレクトが発生します。
ユーザーがログインしていない場合、これは完全に理にかなっています。ただし、ユーザーがalreadyにログインしているが、必要な役割になっていない場合、ログインページにユーザーを送り返すのは混乱します。
AuthorizeAttribute
は認証と承認を制限するようです。
これは、ASP.NET MVCで少し見落としているように見えますか、それとも何か不足していますか?
2つを分離するDemandRoleAttribute
を作成する必要がありました。ユーザーが認証されない場合、HTTP 401を返し、ログインページに送信します。ユーザーがログインしているが、必要な役割になっていない場合、代わりにNotAuthorizedResult
を作成します。現在、これはエラーページにリダイレクトされます。
確かにこれをする必要はありませんでしたか?
最初に開発されたとき、System.Web.Mvc.AuthorizeAttributeは正しいことを行っていました。HTTP仕様の古いリビジョンは、「無許可」と「無認証」の両方にステータスコード401を使用していました。
元の仕様から:
要求にすでに認証資格情報が含まれていた場合、401応答は、それらの資格情報の認証が拒否されたことを示します。
実際、混乱をすぐに見ることができます-「認証」を意味する場合、「承認」という言葉を使用します。ただし、日常のプラクティスでは、ユーザーが認証されているが許可されていない場合は、403 Forbiddenを返す方が理にかなっています。ユーザーがアクセスを許可する資格情報の2番目のセットを持っていることはまずありません。
ほとんどのオペレーティングシステムを検討してください。アクセスする権限がないファイルを読み取ろうとすると、ログイン画面は表示されません。
ありがたいことに、あいまいさを排除するためにHTTP仕様が更新されました(2014年6月)。
「ハイパーテキストトランスポートプロトコル(HTTP/1.1):認証」(RFC 7235)から:
401(Unauthorized)ステータスコードは、ターゲットリソースの有効な認証資格情報がないため、リクエストが適用されていないことを示します。
「ハイパーテキスト転送プロトコル(HTTP/1.1):セマンティクスとコンテンツ」(RFC 7231)から:
403(禁止)ステータスコードは、サーバーが要求を理解したが、承認を拒否したことを示します。
興味深いことに、ASP.NET MVC 1がリリースされた時点で、AuthorizeAttributeの動作は正しいものでした。現在、動作は正しくありません-HTTP/1.1仕様が修正されました。
ASP.NETのログインページリダイレクトを変更しようとするよりも、ソースで問題を修正する方が簡単です。同じ名前(AuthorizeAttribute
)で新しい属性を作成できますWebサイトのデフォルト名前空間で(これは非常に重要です)すると、コンパイラは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);
}
}
}
これをLogin Page_Load関数に追加します。
// User was redirected here because of authorization section
if (User.Identity != null && User.Identity.IsAuthenticated)
Response.Redirect("Unauthorized.aspx");
ユーザーがそこにリダイレクトされているが、既にログインしている場合、不正なページが表示されます。ログインしていない場合は、ログインページが表示されます。
残念ながら、ASP.NETフォーム認証のデフォルトの動作を扱っています。ここで議論されている回避策があります(私は試していません):
http://www.codeproject.com/KB/aspnet/Custon401Page.aspx
(MVCに固有ではありません)
ほとんどの場合、ユーザーがアクセスしようとする前に、不正なリソースへのアクセスを制限することが最善の解決策だと思います。この無許可のページに移動する可能性のあるリンクまたはボタンを削除またはグレーアウトすることにより。
おそらく、許可されていないユーザーをどこにリダイレクトするかを指定するために、属性に追加のパラメーターがあればいいでしょう。しかし、当面は、AuthorizeAttributeをセーフティネットと見なします。
これは理にかなっているといつも思っていました。ログインしていて、自分が持っていないロールを必要とするページにアクセスしようとすると、ログイン画面に転送され、そのロールを持っているユーザーでログインするよう求められます。
ユーザーがすでに認証されているかどうかを確認するロジックをログインページに追加する場合があります。あなたが彼らが再びそこに戻ってきた理由を説明するフレンドリーなメッセージを追加することができます。
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;
}
}
}
}
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");
}
私の場合、問題は「HTTP仕様が「unauthorized」と「unauthenticated」の両方にステータスコード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" });
}