ASP.NET MVC 用户友好的 401 错误

18

我已经在ASP.NET MVC网站中实现了错误处理,类似于这篇帖子的建议

对于404错误,一切都运行良好。但如何正确地显示友好的屏幕来处理 401 错误?它们通常不会抛出可以在Application_Error()内处理的异常,而是动作返回HttpUnauthorizedResult。一种可能的方法是将以下代码添加到Application_EndRequest()方法的末尾。

if (Context.Response.StatusCode == 401)
{
    throw new HttpException(401, "You are not authorised");
    // or UserFriendlyErrorRedirect(new HttpException(401, "You are not authorised")), witout exception
}

但是在 Application_EndRequest() 中,Context.Session == null,导致 errorController.Execute() 失败,因为它无法使用默认的 TempDataProvider。

  // Call target Controller and pass the routeData.
  IController errorController = new ErrorController();
  errorController.Execute(new RequestContext(    
       new HttpContextWrapper(Context), routeData)); // Additional information: The SessionStateTempDataProvider requires SessionState to be enabled.

那么,您能否提供一些ASP.NET MVC应用程序中如何“用户友好处理”401的最佳实践建议?

谢谢。


https://dev59.com/7XE85IYBdhLWcg3w2HXs#2595436 - Cherian
4个回答

14

看看HandleErrorAttribute。从它的子类继承或添加自己的实现,以处理您感兴趣的所有状态代码。您可以使其返回每个错误类型的单独错误视图。

这里是创建处理错误异常过滤器的想法。我已经放弃了大部分内容,只关注我们的要点。一定要查看原始实现以添加参数检查和其他重要事项。

public class HandleManyErrorsAttribute : FilterAttribute, IExceptionFilter
{
    public virtual void OnException(ExceptionContext filterContext)
    {
        if (filterContext.ExceptionHandled || !filterContext.HttpContext.IsCustomErrorEnabled)
            return;

        Exception exception = filterContext.Exception;

        string viewName = string.Empty;
        object viewModel = null;
        int httpCode = new HttpException(null, exception).GetHttpCode();
        if (httpCode == 500)
        {
            viewName = "Error500View";
            viewModel = new Error500Model();
        }
        else if (httpCode == 404)
        {
            viewName = "Error404View";
            viewModel = new Error404Model();
        }
        else if (httpCode == 401)
        {
            viewName = "Error401View";
            viewModel = new Error401Model();
        }

        string controllerName = (string)filterContext.RouteData.Values["controller"];
        string actionName = (string)filterContext.RouteData.Values["action"];
        filterContext.Result = new ViewResult
        {
            ViewName = viewName,
            MasterName = Master,
            ViewData = viewModel,
            TempData = filterContext.Controller.TempData
        };
        filterContext.ExceptionHandled = true;
        filterContext.HttpContext.Response.Clear();
        filterContext.HttpContext.Response.StatusCode = httpCode;

        filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
    }
}

然后你可以使用这个属性来“装饰”你的控制器动作:

[HandleManyErrors]
public ActionResult DoSomethingBuggy ()
{
    // ...
}

据我理解,这个解决方案需要在每个控制器或可能导致401的安全操作(Action)上使用HandleManyErrors属性进行装饰。但是如果能够在全局范围内处理401错误(例如在Global.asax中),那就太好了,或者我理解有误,这是一个不好的想法? - Roman
使用MVC模型时,您并不总是事先知道路由是否正确。一条路由可以解析到某个控制器操作,但在检查数据库中提供的参数后,您可能会决定该带有此参数的路由无法检索所需的资源,因此返回4xx错误。通过这种动态行为,更容易检查事物并从控制器操作中抛出HttpException,因为它们将由属性处理以返回错误页面。 - user151323
对于静态行为,您可以添加最后一个“捕获一切”的路由,以匹配之前定义规则未捕获的所有路由。这个捕获一切的路由将调用一个控制器动作,该动作只会抛出一个 HttpException 异常。这样,您就在 Global.asax 中拥有静态错误处理逻辑,并且在各自的控制器动作中直接进行动态错误处理。 - user151323
@Roman 你可以随时创建一个BaseController并将属性附加到其中。 - harsimranb
@Roman 只需在您的项目中编辑 App_Start\FilterConfig.cs 文件,并在 RegisterGlobalFilters 重写中添加一行代码,如 filters.Add(new HandleManyErrorsAttribute());。这将应用于所有操作。 - Chris Pratt

6

我成功地用一种非常简单的方式解决了这个问题。我想要为已登录用户显示一个自定义页面("您没有权限bla bla..."),并将未经身份验证的用户重定向到登录页面(默认行为)。 因此,我实现了一个自定义的AuthorizeAttribute(称为CustomAuthorizeAttribute),并覆盖了HandleUnauthorizedRequest方法,以便在用户经过身份验证时,我将filterContext参数的Result属性设置为名为AccessDenied.aspx的ViewResult(在Shared文件夹中)。

protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
    if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
    {
        base.HandleUnauthorizedRequest(filterContext);
    }
    else
    {
        filterContext.Result = new ViewResult { ViewName = "AccessDenied" };
    }
}

然后您需要使用这个新属性。谢谢。

对于任何好奇的人,这个属性只在MSDN的Framework 4.0中提到。它在3.5版本中不可用。 - Gregory
1
这可能无法使用“Windows”身份验证。尝试过了,但不幸地失败了。 - Alex Nolasco

0

如果你正在使用ASP.NET MVC,那么很可能会使用IIS,那么为什么不设置IIS来使用你的自定义401错误页面用于该Web应用程序/虚拟目录呢?


我需要针对完整的 Postback 和 AJAX 请求有不同的行为,并且更加掌控错误处理。 - Roman
4
因为您可能想要更精细地控制404页面的呈现方式(包括HTML和HTTP)。您也可能希望在404页面中添加其他信息,例如仅适用于应用程序内的动态内容。您可能还希望使用应用程序日志引擎记录特定消息并跟踪404路径 - 所以所有这些都必须在应用程序内完成。 - Matt Kocaj

-1
在我的一个项目中,我使用来自uvita的代码。
我有ASP.NET MVC2,并且我使用Active Directory身份验证而无需登录页。 我有一个NoAuth.aspx页面,使用站点主页,集成Web应用程序布局。
这是web.config。
<system.web>
    <authentication mode="Windows" />
    <roleManager enabled="true" defaultProvider="AspNetWindowsTokenRoleProvider">
       <providers>
          <clear />
          <add name="AspNetWindowsTokenRoleProvider" type="System.Web.Security.WindowsTokenRoleProvider"
          applicationName="/" />
      </providers>
    </roleManager>
</system.web>

新的CustomAutorizeAttribute类。
using System.Web.Mvc;
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
    {
        if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
        {
            base.HandleUnauthorizedRequest(filterContext);
        }
        else
        {
           filterContext.Result = new ViewResult { ViewName = "NoAuth"};
        }
    }
}

和控制器

[CustomAuthorize(Roles = "ADRole")]
public class HomeController : Controller
{
    public HomeController()
    {
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接