AJAX和FormsAuthentication,如何阻止FormsAuthentication覆盖HTTP 401?

在使用FormsAuthentication配置的一个应用程序中,当用户访问没有auth cookie或过时的访问受保护页面时,ASP.NET会发出HTTP 401 Unauthorized,然后FormsAuthentication模块在请求结束前拦截此响应,并将其更改为HTTP 302 Found,设置HTTP标头“Location:/ path / loginurl”以便将用户代理重定向到登录页面,然后浏览器转到该页面并检索登录页面,该页面未受保护,获取HTTP 200好的。

当AJAX没有被考虑时,这确实是一个非常好的主意。

现在我在我的应用程序中有一个返回JSON数据的URL,它需要用户进行身份validation。 一切运作良好,问题是如果auth cookie到期,当我的客户端代码调用服务器时,它将获得带有登录页面的html的HTTP 200 OK,而不是HTTP 401 Unauthorized(因为之前已解释过)。 然后我的客户端试图将登录页面html解析为json,然后失败。

那么问题是:如何应对来自客户端的过期身份validation? 应对这种情况的最优雅解决方案是什么? 我需要知道调用何时成功,我想使用HTTP语义来实现。

是否可以以安全的跨浏览器方式从客户端读取自定义HTTP标头? 如果请求是AJAX请求,有没有办法告诉FormsAuthenticationModule不执行重定向? 有没有办法使用HTTP标头覆盖HTTP状态,就像覆盖HTTP请求方法一样?

我需要Forms身份validation,我想避免重写该模块或编写我自己的表单身份validation模块。

问候。

我遇到了同样的问题,不得不在MVC中使用自定义属性。 您可以轻松地将其调整为在Web表单中工作,如果所有页面都从某个基页inheritance,则可以覆盖页面在页面中的授权(MVC中的全局属性允许相同的事情 – 覆盖所有控制器/操作的OnAuthorization方法应用)

这是属性的样子:

public class AjaxAuthorizationAttribute : FilterAttribute, IAuthorizationFilter { public void OnAuthorization(AuthorizationContext filterContext) { if (filterContext.HttpContext.Request.IsAjaxRequest() && !filterContext.HttpContext.User.Identity.IsAuthenticated && (filterContext.ActionDescriptor.GetCustomAttributes(typeof(AuthorizeAttribute), true).Count() > 0 || filterContext.ActionDescriptor.ControllerDescriptor.GetCustomAttributes(typeof(AuthorizeAttribute), true).Count() > 0)) { filterContext.HttpContext.SkipAuthorization = true; filterContext.HttpContext.Response.Clear(); filterContext.HttpContext.Response.StatusCode = (int)System.Net.HttpStatusCode.Unauthorized; filterContext.Result = new HttpUnauthorizedResult("Unauthorized"); filterContext.Result.ExecuteResult(filterContext.Controller.ControllerContext); filterContext.HttpContext.Response.End(); } } } 

请注意,您需要调用HttpContext.Response.End(); 或者您的请求将被重定向到登录(因此我丢失了一些头发)。

在客户端,我使用了jQuery ajaxError方法:

 var lastAjaxCall = { settings: null, jqXHR: null }; var loginUrl = "yourloginurl"; //... //... $(document).ready(function(){ $(document).ajaxError(function (event, jqxhr, settings) { if (jqxhr.status == 401) { if (loginUrl) { $("body").prepend("
"); $("div.loginoverlay").show(); lastAjaxCall.jqXHR = jqxhr; lastAjaxCall.settings = settings; } } } }

这显示了iframe在当前页面上的登录(看起来像用户被重定向但你可以使它不同),并且当登录成功时,这个弹出窗口被关闭,原始的ajax请求重新发送:

 if (lastAjaxCall.settings) { $.ajax(lastAjaxCall.settings); lastAjaxCall.settings = null; } 

这允许您的用户在会话到期时登录,而不会丢失他们在上次显示的表单中输入的任何工作或数据。

我从其他post中大量窃取这个答案,但一个想法可能是实现一个HttpModule来拦截重定向到登录页面(该链接的说明)。

您还可以修改该示例HttpModule,只有在请求是通过AJAX进行的时才拦截重定向,如果默认行为是正确的,则请求不是通过AJAX进行的:

检测ajax调用,ASP.net

所以有些东西:

 class AuthRedirectHandler : IHttpModule { #region IHttpModule Members public void Dispose() { } public void Init(HttpApplication context) { context.EndRequest+= new EventHandler(context_EndRequest); } void context_EndRequest(object sender, EventArgs e) { HttpApplication app = (HttpApplication)sender; if (app.Response.StatusCode == 302 && app.Request.Headers["X-Requested-With"] == "XMLHttpRequest" && context.Response.RedirectLocation.ToUpper().Contains("LOGIN.ASPX")) { app.Response.ClearHeaders(); app.Response.ClearContent(); app.Response.StatusCode = 401; } } #endregion } 

如果您的应用中还有其他合法的302重定向,您还可以确保重定向到您的实际登录页面。

然后你只需要添加到你的web.config:

     

无论如何。 再次,实际的原始思想进入了这个答案,我只是从SO和网络的其他部分拉出各种各样的东西。

我在实施已接受的答案时遇到了问题。 首先,我的错误日志充满了Server cannot set status after HTTP headers have been sent错误Server cannot set status after HTTP headers have been sent

我尝试实现已接受的答案问题服务器无法在HTTP标头发送IIS7.5后设置状态 ,再次没有成功。

谷歌搜索了一下我偶然发现了SuppressFormsAuthenticationRedirect 属性

如果.Net版本> = 4.5,则可以将以下代码添加到自定义AuthorizeAttribute类的HandleUnauthorizedRequest方法中。

 public sealed class CustomAuthorizeAttribute : AuthorizeAttribute { protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext) { if (filterContext.HttpContext.Request.IsAjaxRequest()) { filterContext.HttpContext.Response.SuppressFormsAuthenticationRedirect = true; filterContext.HttpContext.Response.TrySkipIisCustomErrors = true; base.HandleUnauthorizedRequest(filterContext); return; } base.HandleUnauthorizedRequest(filterContext); return; } } 

重要的部分是if块。 如果您使用.Net 4.5并且已经拥有自定义授权,这是最简单的方法。