Завершение обработки Cookies сеанса в ASP.NET MVC 3 при использовании запросов WIF и jquery ajax

В моем проекте я использую WIF (но это не очень важно для контекста этого вопроса. Вы можете использовать альтернативную структуру, которая обрабатывает вашу аутентификацию. Вопрос в том, чтобы справляться с ошибками аутентификации при выполнении запросов ajax). Тем не менее, в моем случае я написал пользовательскую логику сервера, которая наследует от ClaimsAuthenticationManager и обрабатывает аутентификацию:

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

Теперь, после того как я удалю все Cookies сеанса, закончите, затем снова введите любую страницу, я перенаправлен на страницу входа в систему, обслуживаемую WIF, и мне будет предложено снова зарегистрироваться. Все работает так, как ожидалось.

Но если я сделаю запрос ajax, у меня есть ошибка, которая перехвачена этим:

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

К сожалению, объект XMLHttpRequest не возвращает никакого значимого сообщения, на основе которого я мог бы обрабатывать такую ​​ошибку любым другим способом, как другие. В этом конкретном случае я просто хочу, чтобы приложение перенаправлялось на страницу входа - как это делает обычный запрос.

enter image description here

Пока выполняется вызов ajax, вызывается метод Authenticate from ClaimsAuthenticationManager. Identity.IsAuthenticated возвращает false, конец метода и все делается. Даже метод OnAuthorization из 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);
}

Как решить головоломку?

Ответ 1

Я нашел некоторые ресурсы об этом (см. нижнюю часть ответа) и перепутал следующее решение:

во время выполнения 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
        }
    });
});

Смотрите Как управлять запросом перенаправления после вызова jQuery Ajax и здесь Как вы работаете с запросами AJAX, когда пользователь сеанс истекает, или когда запрос завершается в 302 для получения дополнительных пояснений.

UPDATE:

Между тем, я понял, что новое решение, на мой взгляд, намного лучше, потому что оно может применяться ко всем запросам 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() остается прежним.