为什么AuthorizeAttribute认证授权失败会重定向到登录页面?
-
04-07-2019 - |
题
在 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
反而。目前,这会重定向到错误页面。
我当然不必这样做吗?
解决方案
首次开发时,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);
}
}
}
其他提示
将此添加到您的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视为一个安全网。
在Global.asax文件的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");
}
如果您使用的是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;
}
}
}
}
在我的情况下,问题是“HTTP规范使用状态代码401用于”未授权的“和“未经认证的”&quot;。正如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" });
}