Web应用中的认证与请求处理

在开发Web应用时,经常会遇到各种问题,其中一些与用户认证和处理客户端请求有关。这些问题可能会让头疼不已。本文将探讨这些问题,并提供一些解决方案。

问题描述

假设有一个Web应用,它通过jQuery和JSON与服务器进行通信。服务器端的代码如下:

[HttpPost] public ActionResult GetData() { return Json(new { Items = new[] { "Li Chen", "Abdullah Khamir", "Mark Schrenberg", "Katy Sullivan", "Erico Gantomaro", } }); }

客户端的代码如下:

var $list = $("#list"); var $status = $("#status"); $list.empty(); $status.text("Loading..."); $.post("/home/getdata") .always(function() { $status.empty(); }) .success(function(data) { for (var i = 0; i < data.Items.length; i++) { $list.append($("").text(data.Items[i])); } });

这是一个非常简单的例子。现在让为Web应用添加一个简单的认证机制,使用ASP.NET MVC的Forms Authentication和Authorize属性。控制器的代码将变为:

[HttpPost] [Authorize] public ActionResult GetData() { return Json(new { Items = new[] { "Li Chen", "Abdullah Khamir", "Mark Schrenberg", "Katy Sullivan", "Erico Gantomaro", } }); }

在用户通过认证后,他们可以查看页面并检索数据。然而,当认证超时过期时,会出现一些问题。在这种情况下,服务器会向用户发送HTTP 302 Found响应。显然,客户端代码没有预料到这种情况,因此应用将无法正常工作。

原因分析

在修复代码之前,让先了解为什么会发生这种情况。为什么服务器会发送HTTP 302?可能会认为应该是HTTP 401。问题的关键在于,当使用FormsAuthentication时,背后实际上是FormsAuthenticationModule在起作用(它默认在全局web.config文件中注册)。深入研究该模块的内部,可以很容易地理解,如果当前的HTTP状态码是401,那么它会执行重定向,即将其替换为302:

这样做的目的是,如果请求没有成功处理(状态码为401),则将用户重定向到登录页面。在这种情况下,用户将看到一个友好的登录表单,而不是IIS错误代码。这很有意义,不是吗?

从ASP.NET MVC应用的角度来看,处理流程如下:

一个请求进入应用,遇到了AuthorizeAttribute过滤器。由于用户未通过认证,因此该过滤器会返回HTTP 401,这是合乎逻辑的(可以通过使用反射器并检查该过滤器的实现来轻松看到这一点)。

然后,FormsAuthenticationModule起作用,将HTTP 401替换为重定向。

结果就是,当使用普通的HTTP请求请求页面时,会看到登录页面(这是好的),但当使用AJAX调用时,解析这样的响应就变得非常困难(这是不好的)。

需要解决的问题是:

  • 服务器应该为AJAX调用返回HTTP 401/403,为普通HTTP调用返回HTTP 302。
  • 在客户端处理HTTP 401/403。

为了实现这一点,可以覆盖FormsAuthenticationModule的逻辑,使其不要将HTTP 401请求替换为302。要做到这一点,可以使用SuppressFormsAuthenticationRedirect属性:

public class ApplicationAuthorizeAttribute : AuthorizeAttribute { protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext) { var httpContext = filterContext.HttpContext; var request = httpContext.Request; var response = httpContext.Response; if (request.IsAjaxRequest()) { response.SuppressFormsAuthenticationRedirect = true; base.HandleUnauthorizedRequest(filterContext); } } }

接下来,添加一个条件:如果用户已认证,则发送HTTP 403;否则发送HTTP 401。

public class ApplicationAuthorizeAttribute : AuthorizeAttribute { protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext) { var httpContext = filterContext.HttpContext; var request = httpContext.Request; var response = httpContext.Response; var user = httpContext.User; if (request.IsAjaxRequest()) { if (user.Identity.IsAuthenticated == false) response.StatusCode = (int)HttpStatusCode.Unauthorized; else response.StatusCode = (int)HttpStatusCode.Forbidden; response.SuppressFormsAuthenticationRedirect = true; response.End(); } base.HandleUnauthorizedRequest(filterContext); } }

现在应该替换所有使用标准AuthorizeAttribute的地方,使用这个新的过滤器。这可能不适用于一些对代码有审美要求的人,但不知道还有其他方法。如果有,请在评论中告诉。

最后,应该在客户端添加HTTP 401/403的处理。可以使用jQuery的ajaxError来避免代码重复:

$(document).ajaxError(function (e, xhr) { if (xhr.status == 401) window.location = "/Account/Login"; else if (xhr.status == 403) alert("You have no enough permissions to request this resource."); });
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485