4

我的项目我正在使用 WIF (但这对于这个问题的上下文并不重要。您可以使用替代框架来处理您的身份验证。问题是关于在执行 ajax 请求时处理身份验证失败)。不过,就我而言,我编写了自定义服务器逻辑,它继承自ClaimsAuthenticationManager并处理身份验证:

public override IClaimsPrincipal Authenticate(string resourceName, IClaimsPrincipal incomingPrincipal)
{
    if (incomingPrincipal != null && incomingPrincipal.Identity.IsAuthenticated)
    {
        // add some custom claims
    }
    return incomingPrincipal;
}

现在,在我删除所有Session Cookies后,结束然后再次进入任何页面,我被重定向到 WIF 提供的登录页面,并要求我再次登录。一切都按预期工作。

但是,如果我改为发出ajax 请求,则会出现错误,并被以下代码拦截:

$(document).ready(function () {
    $.ajaxSetup({
        error: function (XMLHttpRequest, textStatus, errorThrown) {            
            // do something
        }
    });
});

不幸的是XMLHttpRequest,对象没有返回任何有意义的消息,基于这些消息,我可以像其他人一样以任何其他方式处理这种错误。在这种特殊情况下,我只希望应用程序重定向到登录页面 - 就像正常请求一样。

在此处输入图像描述

当 ajax 调用正在执行时,方法AuthenticatefromClaimsAuthenticationManager被调用。Identity.IsAuthenticated返回false,方法结束,一切都完成了。即使OnAuthorizationfrom 方法BaseController也没有被调用,所以我不能将任何状态传递给 ajax 结果对象。

protected override void OnAuthorization(AuthorizationContext filterContext)
{
    if (filterContext.HttpContext.Request.IsAjaxRequest() && !User.Identity.IsAuthenticated)
    {
        //do something, for example pass custom result to filterContext
    }
    base.OnAuthorization(filterContext);
}

如何解决难题?

4

1 回答 1

7

我找到了一些关于这个的资源(见答案的底部),并与以下解决方案混为一谈:

在执行 ajax 请求时,我指定要返回 json:

$.ajax({
    url: action,
    type: 'POST',
    dataType: 'json',
    data: jsonString,
    contentType: 'application/json; charset=utf-8',
    success:
        function (result, textStatus, xhr) {
        }
});

因为我的框架处理身份验证,当令牌过期时,它会将 http 状态 302 放入响应中。因为我不希望我的浏览器透明地处理 302 响应,所以我在 Global.asax 中捕获它,并将状态更改为 200 OK。此外,我添加了标头,指示我以特殊方式处理此类响应:

protected void Application_EndRequest()
{
    if (Context.Response.StatusCode == 302
        && (new HttpContextWrapper(Context)).Request.IsAjaxRequest())
    {                
        Context.Response.StatusCode = 200;
        Context.Response.AddHeader("REQUIRES_AUTH", "1");
    }
}

响应内容未正确序列化为 json,导致解析错误。调用错误事件,在其中执行重定向:

$(document).ready(function () {
    $.ajaxSetup({
        error: function (XMLHttpRequest, textStatus, errorThrown) {
            if (XMLHttpRequest.getResponseHeader('REQUIRES_AUTH') === '1') {
                // redirect to logon page
                window.location = XMLHttpRequest.getResponseHeader('location');
            }
            // do sth else
        }
    });
});

请参阅How to manage a redirect request after a jQuery Ajax call and here How do you deal with AJAX requests when user session expires, or where the request terminates in a 302以获得更多解释。

更新:

同时,我想出了新的解决方案,在我看来要好得多,因为可以开箱即用地应用于所有 ajax 请求(如果他们没有明显地重新定义 beforeSend 事件):

$.ajaxSetup({
    beforeSend: checkPulse,
    error: function (XMLHttpRequest, textStatus, errorThrown) {
        document.open();
        document.write(XMLHttpRequest.responseText);
        document.close();
    }
});

function checkPulse(XMLHttpRequest) {
    var location = window.location.href;
    $.ajax({
        url: "/Controller/CheckPulse",
        type: 'GET',
        async: false,
        beforeSend: null,
        success:
            function (result, textStatus, xhr) {
                if (xhr.getResponseHeader('REQUIRES_AUTH') === '1') {
                    XMLHttpRequest.abort(); // terminate further ajax execution
                    window.location = location;
                }
            }
    });
}

控制器方法可以是任何最简单的方法:

[Authorize]
public virtual void CheckPulse() {}

Application_EndRequest()保持和以前一样。

于 2012-06-18T08:02:21.700 回答