为什么AuthorizeAttribute重定向到登录页面以进行身份​​验证和授权失败?


265

在ASP.NET MVC中,您可以使用标记控制器方法AuthorizeAttribute,如下所示:

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

这意味着,如果当前登录的用户不具有“ CanDeleteTags”角色,则将永远不会调用控制器方法。

不幸的是,对于失败,AuthorizeAttribute返回HttpUnauthorizedResult,始终返回HTTP状态代码401。这将导致重定向到登录页面。

如果用户未登录,则非常合理。但是,如果用户已经登录,但没有所需的角色,则将他们发送回登录页面会造成混乱。

似乎AuthorizeAttribute将认证和授权混为一谈。

这似乎是对ASP.NET MVC的疏忽,还是我错过了一些东西?

我不得不煮熟DemandRoleAttribute将两者分开。当用户未通过身份验证时,它将返回HTTP 401,并将其发送到登录页面。当用户登录但没有所需角色时,它将创建一个NotAuthorizedResult。当前,这将重定向到错误页面。

当然我不必这样做吗?


10
很好的问题,我同意,它应该抛出HTTP未授权状态。
Pure.Krome 2010年

3
罗杰,我喜欢您的解决方案。即使你不这样做。
乔恩·戴维斯

我/她的登录页面有一个检查,可以简单地将用户重定向到ReturnUrl(如果已将其重定向)。因此,我设法创建了302重定向:D woot的无限循环。
juhan_h 2011年

1
看看 这个
Jogi,2016年

罗杰,您的解决方案上的好文章-red-gate.com/simple-talk/dotnet/asp-net / ...看来您的解决方案是唯一做到这一点的唯一方法
Craig

Answers:


305

最初开发时,System.Web.Mvc.AuthorizeAttribute做正确的事-HTTP规范的旧版本对“未经授权”和“未经身份验证”使用状态码401。

从原始规格:

如果请求已包含授权凭证,则401响应指示已拒绝这些凭证的授权。

实际上,您可以在此处看到混乱-它在表示“身份验证”时使用“授权”一词。但是,在日常实践中,当用户经过身份验证但未经授权时,返回403 Forbidden是更有意义的。用户不太可能拥有第二组凭据来授予他们访问权限-糟糕的用户体验。

考虑大多数操作系统-尝试读取您无权访问的文件时,不会显示登录屏幕!

值得庆幸的是,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的登录页面重定向,还不如尝试从源头上解决问题,这更容易。您可以在网站的默认名称空间中创建一个具有相同名称(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,其中有自带的null检查见。stackoverflow.com/questions/1379566/...
丹尼尔·里尤兹

>您可以在网站的默认名称空间中创建一个具有相同名称(AuthorizeAttribute)的新属性,然后编译器将自动选择它,而不是MVC的标准属性。这将导致错误:找不到类型或名称空间“ Authorize”(您是否缺少指令或程序集引用?)两者都使用System.Web.Mvc; 控制器中引用了我的自定义AuthorizeAttribute类的名称空间。为了解决这个问题,我不得不使用[MyNamepace.Authorize]
stormwild 2011年

2
@DePeter规范从不对重定向进行任何说明,那么为什么重定向是一种更好的解决方案?仅此一项就杀死了ajax请求,而没有适当的解决方法。
Adam Tuliper-MSFT 2012年

1
应该将其记录在MS Connect上,因为它显然是行为错误。谢谢。
托尼·沃尔

顺便说一句,为什么我们重定向到登录页面?为什么不直接在同一请求中直接输出401代码和登录页面?
SandRock 2014年

25

将此添加到您的登录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-然后在默认的ActionMethod中,对于已设置要调用FormsAuthencation的调用的控制器执行此操作。
Pure.Krome 2010年

其实,这工作真的很好,虽然对MVC它应该是这样的if (User.Identity != null && User.Identity.IsAuthenticated) return RedirectToRoute("Unauthorized");,其中未经授权是定义的路径名。
Moses Machua 2013年

因此,您问一个资源,您被重定向到登录页面,然后被重定向到403页面?对我来说似乎很糟糕。我什至不能容忍一次重定向。海事组织这东西反正非常糟糕。
SandRock 2014年

3
根据您的解决方案,如果您已经登录并通过输入URL进入“登录”页面...,这将使您进入“未经授权”页面。这是不对的。
Rajshekar Reddy 2015年

4

我一直认为这确实有意义。如果您已登录,并且尝试访问一个页面,该页面需要一个您没有的角色,则您将转到登录屏幕,要求您与具有该角色的用户一起登录。

您可以在登录页面中添加逻辑,以检查用户是否已通过身份验证。您可以添加一条友好的消息,以说明为什么再次将它们再次闷闷不乐。


4
我的感觉是,对于给定的Web应用程序,大多数人往往不会拥有多个身份。如果他们这样做,那么他们足够聪明,以为“我当前的ID没有mojo,我将以另一个身份登录”。
罗杰·利普斯科姆

尽管关于在登录页面上显示某些内容的其他要点也很不错。谢谢。
罗杰·利普斯科姆

4

不幸的是,您正在处理ASP.NET表单身份验证的默认行为。这里讨论一种解决方法(我没有尝试过):

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

(并非特定于MVC)

我认为在大多数情况下,最好的解决方案是在用户尝试到达未授权资源之前限制对未授权资源的访问。删除/删除可能会将其带到此未经授权页面的链接或按钮。

在属性上具有一个附加参数来指定将未经授权的用户重定向到何处可能会很好。但是与此同时,我将AuthorizeAttribute视为安全网。


我计划也基于授权删除链接(我在这里看到了关于该位置的问题),因此稍后我将编写HtmlHelper扩展方法。
罗杰·利普斯科姆

1
我仍然必须防止用户直接转到URL,这就是此属性的全部含义。我对Custom 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" });
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.