Обработка истечения срока действия файлов cookie сеанса в ASP.NET MVC 3 при использовании запросов ajax WIF и jquery

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

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

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

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

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

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

Пока выполняется вызов ajax, метод Authenticate от 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
        }
    });
});

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

Другие вопросы по тегам