Обработка истечения срока действия файлов 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()
остается таким же, как и раньше.