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

vto*_*ola 26 asp.net ajax jquery forms-authentication http

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

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

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

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

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

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

问候.

Gor*_*vic 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();
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

请注意,您需要调用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;
                }
            }
    }

}
Run Code Online (Sandbox Code Playgroud)

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

if (lastAjaxCall.settings) {
        $.ajax(lastAjaxCall.settings);
        lastAjaxCall.settings = null;
    }
Run Code Online (Sandbox Code Playgroud)

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

  • 我不相信浏览器缓存401错误:)其余的代码没有任何授权,Galloway的文章处理CUSTOMIZING身份验证/授权过程.这里没有自定义,这段代码的作用是检查请求是否为ajax,如果是,则返回空401响应而不是登录页面,这样你就可以在iframe中显示登录页面,或者你想要防止用户丢失工作上下文和任何他所在页面的内容.授权仍然使用mvc的AuthorizeAttribute执行,如果没有AuthorizeAttribute,则不执行任何此代码. (2认同)

Kev*_*ker 5

我大量偷这个答案来自其他职位,但一个想法可能是实施HttpModule拦截重定向到登录页面(该链接说明).

如果请求是通过AJAX进行的,如果默认行为在没有通过AJAX发出时是正确的,你也可以修改该示例HttpModule来仅拦截重定向:

检测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
}
Run Code Online (Sandbox Code Playgroud)

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

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

  <httpModules>
    <add name="AuthRedirectHandler" type="SomeNameSpace.AuthRedirectHandler, SomeNameSpace" />
  </httpModules>
Run Code Online (Sandbox Code Playgroud)

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

  • 1.你不是"重写HTTP模块",你正在添加一个非常简短的(不处理任何实际身份验证)来处理未经授权的AJAX请求和2.正如我所说,该示例不包括可以想象的每个过滤器(在这种情况下甚至是可取的)但是登录页面的302重定向要么是a)登录页面,要么是b)web.config中设置的值.这些值中的任何一个都应该很容易理解并包含在条件中.我认为你不会找到一个涉及不到十几行代码的解决方案. (2认同)
  • 好吧,我回答了你的问题:"如果请求是一个AJAX请求,有没有办法告诉FormsAuthenticationModule不执行重定向?" 对不起,您不满意这样做是一个简单的HTTP模块.此外,SO上的任何人都不愿意为任何旧问题添加新的解决方案.相信我,如果有一个神奇的子弹,它将被解决到某个地方的旧问题. (2认同)

Ami*_*rge 5

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

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

谷歌搜索了一下我偶然发现了该SuppressFormsAuthenticationRedirect 物业

如果.Net版本> = 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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