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

时间:2011-09-23 16:42:46

标签: asp.net ajax http jquery forms-authentication

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

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

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

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

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

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

问候。

3 个答案:

答案 0 :(得分:23)

我遇到了同样的问题,不得不在MVC中使用自定义属性。您可以轻松地将其调整为在Web表单中工作,如果所有页面都从某个基页继承,则可以覆盖页面在页面中的授权(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 class='loginoverlay'><div class='full'></div><div class='iframe'><iframe id='login' src='" + loginUrl + "'></iframe></div></div>");
                    $("div.loginoverlay").show();
                    lastAjaxCall.jqXHR = jqxhr;
                    lastAjaxCall.settings = settings;
                }
            }
    }

}

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

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

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

答案 1 :(得分:5)

我从其他帖子中大量窃取了这个答案,但一个想法可能是在登录页面上实现HttpModuleintercept the redirect(该链接上的说明)。

如果请求是通过AJAX进行的,如果请求不是通过AJAX进行的,默认行为是正确的,那么您也可以修改该示例HttpModule以仅拦截重定向:

Detect ajax call, 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:

  <httpModules>
    <add name="AuthRedirectHandler" type="SomeNameSpace.AuthRedirectHandler, SomeNameSpace" />
  </httpModules>

总之。再一次,实际的原始思想进入了这个答案,我只是从SO和网络的其他部分把各种各样的东西拉到一起。

答案 2 :(得分:5)

我在实施accepted answer时遇到了问题。主要是,我的错误日志充满Server cannot set status after HTTP headers have been sent错误。

我尝试实施accepted answer来质疑Server cannot set status after HTTP headers have been sent IIS7.5,但再次没有成功。

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

如果您的.Net版本是&gt; = 4.5,那么您可以将以下代码添加到自定义HandleUnauthorizedRequest类的AuthorizeAttribute方法中。

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&amp;已经有自定义授权。