273

在 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。目前这会重定向到错误页面。

当然我不必这样做?

4

7 回答 7

311

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

从原始规范:

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

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

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

值得庆幸的是,HTTP 规范已更新(2014 年 6 月)以消除歧义。

来自“超文本传输​​协议(HTTP/1.1):身份验证”(RFC 7235):

401(未授权)状态码表示该请求尚未应用,因为它缺少目标资源的有效身份验证凭据。

来自“超文本传输​​协议 (HTTP/1.1):语义和内容”(RFC 7231):

403(Forbidden)状态码表示服务器理解请求但拒绝授权。

有趣的是,在 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);
        }
    }
}
于 2011-04-30T22:00:22.260 回答
24

将此添加到您的 Login Page_Load 函数中:

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

当用户被重定向到那里但已经登录时,它会显示未经授权的页面。如果他们没有登录,它会通过并显示登录页面。

于 2009-04-01T13:29:29.310 回答
4

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

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

(它不是特定于 MVC)

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

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

于 2008-10-26T22:02:54.477 回答
4

我一直认为这确实有道理。如果您已登录并尝试访问需要您没有角色的页面,您将被转发到登录屏幕,要求您使用具有该角色的用户登录。

您可以向登录页面添加逻辑,以检查用户是否已通过身份验证。您可以添加一条友好的消息,解释为什么他们又被撞回了那里。

于 2008-10-27T16:28:46.647 回答
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");
}
于 2015-01-08T21:44:27.313 回答
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;
            }
        }
    }
}
于 2018-02-14T23:05:55.623 回答
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" });
}
于 2019-03-18T18:38:48.977 回答