Как управлять запросом на перенаправление после вызова jQuery Ajax

1370

Я использую $.post()для вызова сервлета Ajax, а затем использую полученный HTML-фрагмент для замены divэлемента на текущей странице пользователя. Однако, если время сеанса истекло, сервер отправляет директиву перенаправления, чтобы отправить пользователя на страницу входа. В этом случае jQuery заменяет divэлемент содержимым страницы входа в систему, заставляя пользователя увидеть действительно редкую сцену.

Как я могу управлять директивой перенаправления от вызова Ajax с jQuery 1.2.6?

Эллиот Варгас
источник
1
(не ответ как таковой) - я делал это в прошлом, редактируя библиотеку jquery и добавляя проверку страницы входа в систему при каждом завершении XHR. Не лучшее решение, потому что оно должно выполняться при каждом обновлении, но оно решает проблему.
Sugendran
1
См. Связанный вопрос: stackoverflow.com/questions/5941933/…
Nutel
HttpContext.Response.AddHeaderИ проверка на ajaxsetup Sucess является путь
LCJ
4
Почему сервер не может вернуть 401? В этом случае вы можете использовать глобальный $ .ajaxSetup и использовать код состояния для перенаправления страницы.
Вишал
1
эта ссылка doanduyhai.wordpress.com/2012/04/21/… дает мне правильное решение
pappu_kutty

Ответы:

697

Я прочитал этот вопрос и реализовал подход, который был заявлен в отношении установки кода состояния ответа HTTP на 278, чтобы браузер прозрачно не обрабатывал перенаправления. Несмотря на то, что это сработало, я был немного недоволен, так как это немного хакерство.

После дальнейших исследований я отказался от этого подхода и использовал JSON . В этом случае все ответы на запросы AJAX имеют код состояния 200, а тело ответа содержит объект JSON, созданный на сервере. Затем JavaScript на клиенте может использовать объект JSON, чтобы решить, что ему нужно делать.

У меня была похожая проблема с твоей. Я выполняю AJAX-запрос, который имеет 2 возможных ответа: один перенаправляет браузер на новую страницу, а другой заменяет существующую HTML-форму на текущей странице новой. Код jQuery для этого выглядит примерно так:

$.ajax({
    type: "POST",
    url: reqUrl,
    data: reqBody,
    dataType: "json",
    success: function(data, textStatus) {
        if (data.redirect) {
            // data.redirect contains the string URL to redirect to
            window.location.href = data.redirect;
        } else {
            // data.form contains the HTML for the replacement form
            $("#myform").replaceWith(data.form);
        }
    }
});

Объект JSON «данные» создается на сервере, чтобы иметь 2 члена: data.redirectи data.form. Я нашел этот подход намного лучше.

Steg
источник
58
Как указано в решении в stackoverflow.com/questions/503093/…, лучше использовать window.location.replace (data.redirect); чем window.location.href = data.redirect;
Карлес Барробес
8
Любая причина, почему это не было бы лучше использовать HTTP-коды в действии. Например, код 307, который является временным перенаправлением HTTP?
Сергей Голос
15
@Sergei Golos, причина в том, что если вы выполняете HTTP-перенаправление, оно фактически никогда не поступает в обратный вызов ajax success. Браузер обрабатывает перенаправление, предоставляя код 200 с содержимым места назначения перенаправления.
Мигель Сильва
2
как будет выглядеть код сервера? иметь возвращаемое значение data.form и data.redirect. в основном, как я могу определить, положил ли я в него перенаправление?
Vnge
6
Этот ответ был бы более полезным, если бы он показал, как это делается на сервере.
Педро Хоэль Карвалью
244

Я решил эту проблему:

  1. Добавление пользовательского заголовка к ответу:

    public ActionResult Index(){
        if (!HttpContext.User.Identity.IsAuthenticated)
        {
            HttpContext.Response.AddHeader("REQUIRES_AUTH","1");
        }
        return View();
    }
  2. Привязка функции JavaScript к ajaxSuccessсобытию и проверка, существует ли заголовок:

    $(document).ajaxSuccess(function(event, request, settings) {
        if (request.getResponseHeader('REQUIRES_AUTH') === '1') {
           window.location = '/';
        }
    });
SuperG
источник
6
Какое потрясающее решение. Мне нравится идея универсального решения. Мне нужно проверить состояние 403, но я могу использовать для этого привязку ajaxSuccess для тела (именно это я и искал). Спасибо.
Бреттикус
4
Я просто сделал это и обнаружил, что мне нужен ajaxComplete, где я использовал функцию $ .get (), и любой статус, кроме 200, не срабатывал. На самом деле, я мог бы просто связать с ajaxError вместо этого. Смотрите мой ответ ниже для более подробной информации.
Бреттикус
2
Во многих случаях это нормально, но что, если ваш фреймворк обрабатывает авторизацию?
jwaliszko
13
Мне нравится подход заголовка, но я также думаю - как @ mwoods79 - что знание того, куда перенаправлять, не должно дублироваться. Я решил это, добавив заголовок REDIRECT_LOCATION вместо логического.
rintcius
3
Постарайтесь установить заголовок ответа ПОСЛЕ перенаправления. Как описано в других ответах на этой странице, перенаправление может быть прозрачным для обработчика ajaxSucces. Таким образом, я включил заголовок в ответ GET на странице входа в систему (которая в конечном итоге была единственной, запускающей ajaxSuccess в моем сценарии).
sieppl
118

Ни один браузер не обрабатывает ответы 301 и 302 правильно. И на самом деле в стандарте даже говорится, что они должны обращаться с ними «прозрачно», что является огромной головной болью для поставщиков Ajax Library. В Ra-Ajax мы были вынуждены использовать код состояния ответа HTTP 278 (просто некоторый «неиспользованный» код успеха) для прозрачной обработки перенаправлений с сервера ...

Это действительно раздражает меня, и если у кого-то здесь есть некоторая «тяга» в W3C, я был бы признателен, если бы вы сообщили W3C , что нам действительно нужно обрабатывать коды 301 и 302 самостоятельно…! ;)

Томас Хансен
источник
3
Я за один ход, что 278 должен стать частью официальной спецификации HTTP.
Крис Марисик
2
Разве это уже не обрабатывает их прозрачно? Если ресурс переместился, прозрачная обработка означает повторение запроса по предоставленному URL. Это то, что я ожидаю, используя API XMLHttpRequest.
Филипп Рате
@ PhilippeRathé Согласен. Прозрачная обработка - это то, что я хочу. И я не знаю, почему это считается плохим.
smwikipedia
@smwikipedia Организовать перенаправление разметки в главном разделе без перенаправления страницы.
CMC
если у кого-то здесь есть какое-то «притяжение» в W3C, я был бы признателен, чтобы вы сообщили W3C, что нам действительно нужно обрабатывать коды 301 и 302 самим ...! ;)
Tommy.Tang
100

Решение, которое в конечном итоге было реализовано, заключалось в использовании оболочки для функции обратного вызова вызова Ajax, и в этой оболочке проверяется наличие определенного элемента в возвращенном фрагменте HTML. Если элемент был найден, то оболочка выполняет перенаправление. Если нет, то оболочка перенаправила вызов в функцию фактического обратного вызова.

Например, наша функция-обертка была что-то вроде:

function cbWrapper(data, funct){
    if($("#myForm", data).length > 0)
        top.location.href="login.htm";//redirection
    else
        funct(data);
}

Затем при вызове Ajax мы использовали что-то вроде:

$.post("myAjaxHandler", 
       {
        param1: foo,
        param2: bar
       },
       function(data){
           cbWrapper(data, myActualCB);
       }, 
       "html"
);

Это сработало для нас, потому что все вызовы Ajax всегда возвращали HTML внутри элемента DIV, который мы используем для замены части страницы. Кроме того, нам нужно было только перенаправить на страницу входа.

Эллиот Варгас
источник
4
Обратите внимание, что это можно сократить до функции cbWrapper (funct) {return function (data) {if ($ ("# myForm", data) .size ()> 0) top.location.href = "login"; еще функция (данные); }}. Тогда вам нужен только cbWrapper (myActualCB) при вызове .post. Да, код в комментариях - беспорядок, но это следует отметить :)
Симен Эххолт
размер обесценивается, так что вы можете использовать длину здесь вместо размера
sunil
66

Мне нравится метод Тиммерца с легким поворотом лимона. Если вы когда - нибудь вернулся CONTENTTYPE из текста / HTML , когда вы ожидаете JSON , вы, скорее всего перенаправлены. В моем случае я просто перезагружаю страницу, и она перенаправляется на страницу входа. О, и проверьте, что статус jqXHR равен 200, что кажется глупым, потому что вы находитесь в функции ошибок, верно? В противном случае допустимые ошибки приводят к повторной перезагрузке (упс)

$.ajax(
   error:  function (jqXHR, timeout, message) {
    var contentType = jqXHR.getResponseHeader("Content-Type");
    if (jqXHR.status === 200 && contentType.toLowerCase().indexOf("text/html") >= 0) {
        // assume that our login has expired - reload our current page
        window.location.reload();
    }

});
BrianY
источник
1
Большое спасибо, Брайан, ваш ответ был лучшим для моего сценария, хотя я хотел бы, чтобы была более безопасная проверка, например, сравнение того, на какой URL / страницу перенаправляется, вместо простой проверки типа содержимого. Я не смог найти, на какую страницу перенаправляется объект jqXHR.
Джонни
Я проверил на статус 401, а затем перенаправить. Работает как чемпион.
Эрик
55

Используйте низкоуровневый $.ajax()вызов:

$.ajax({
  url: "/yourservlet",
  data: { },
  complete: function(xmlHttp) {
    // xmlHttp is a XMLHttpRquest object
    alert(xmlHttp.status);
  }
});

Попробуйте это для перенаправления:

if (xmlHttp.code != 200) {
  top.location.href = '/some/other/page';
}
Пока
источник
Я пытался избежать вещей низкого уровня. Во всяком случае, предположим, что я использую что-то вроде того, что вы описываете, как я могу принудительно перенаправить браузер, как только я обнаружу, что код HTTP 3xx? Моя цель - перенаправить пользователя, а не просто сообщить, что его сеанс истек.
Эллиот Варгас
4
Кстати, $ .ajax () не очень, очень низкоуровневый. Он просто низкоуровневый с точки зрения jQuery, потому что есть $ .get, $ .post и т. Д., Которые намного проще, чем $ .ajax и все его параметры.
до
16
О, парень! Извините, я должен был "принять" ваш ответ, он все еще очень полезен. Дело в том, что перенаправления автоматически управляются запросом XMLHttpRequest, поэтому я ВСЕГДА получаю код состояния 200 после перенаправления (вздох!). Я думаю, что мне придется сделать что-то неприятное, например, анализ HTML и поиск маркера.
Эллиот Варгас
1
Просто любопытно, если сеанс заканчивается на сервере, не означает ли это, что сервер отправляет другой SESSIONID? мы не могли просто обнаружить это?
Salamander2007
10
ПРИМЕЧАНИЕ. Это не работает для перенаправлений. ajax перейдет на новую страницу и вернет код статуса.
33

Я просто хотел поделиться своим подходом, так как это может кому-то помочь:

Я в основном включил модуль JavaScript, который обрабатывает такие вещи, как отображение имени пользователя, а также этот случай, обрабатывая перенаправление на страницу входа .

Мой сценарий: у нас в основном ISA-сервер, который слушает все запросы и отвечает 302 и заголовком местоположения на нашей странице входа.

В моем модуле JavaScript мой первоначальный подход был что-то вроде

$(document).ajaxComplete(function(e, xhr, settings){
    if(xhr.status === 302){
        //check for location header and redirect...
    }
});

Проблема (как многие здесь уже упоминали) заключается в том, что браузер обрабатывает перенаправление самостоятельно, поэтому мой ajaxCompleteобратный вызов никогда не вызывался, но вместо этого я получил ответ от уже перенаправленной страницы входа, которая, очевидно, былаstatus 200 . Проблема: как вы определяете, является ли успешный ответ 200 вашей реальной страницей входа или просто какой-то другой произвольной страницей?

Решение

Так как я не смог перехватить 302 ответа на перенаправление, я добавил LoginPageна свою страницу входа заголовок, который содержал URL самой страницы входа. В модуле я сейчас слушаю заголовок и делаю редирект:

if(xhr.status === 200){
    var loginPageRedirectHeader = xhr.getResponseHeader("LoginPage");
    if(loginPageRedirectHeader && loginPageRedirectHeader !== ""){
        window.location.replace(loginPageRedirectHeader);
    }
}

... и это работает как шарм :). Вы можете удивиться, почему я включил URL в LoginPageзаголовок ... ну, в основном, потому что я не нашел способа определить URL в GETрезультате автоматического перенаправления местоположения с xhrобъекта ...

Juri
источник
+1 - но пользовательские заголовки должны начинаться с X-, поэтому лучше использовать заголовок X-LoginPage: http://example.com/login.
uınbɐɥs
6
@ShaquinTrifonoff Больше нет. Я не использовал префикс X, потому что в июне 2011 года в документе ITEF предлагалось объявить их устаревшими, и с июня 2012 года официальные заголовки больше не должны иметь префикса X-.
Юрий
У нас также есть ISA-сервер, и я столкнулся с той же проблемой. Вместо того, чтобы обойти это в коде, мы использовали инструкции в kb2596444, чтобы настроить ISA для остановки перенаправления.
Скотт
29

Я знаю, что эта тема старая, но я дам еще один подход, который я нашел и ранее описал здесь . В основном я использую ASP.MVC с WIF (но это не очень важно для контекста этой темы - ответ адекватен независимо от того, какие платформы используются. Ключ остается неизменным - решение проблем, связанных с ошибками аутентификации при выполнении запросов AJAX ) .

Подход, показанный ниже, может применяться ко всем Ajax-запросам из коробки (если они явно не переопределяют событие beforeSend).

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

Перед выполнением любого ajax-запроса вызывается CheckPulseметод (метод контроллера, который может быть простым):

[Authorize]
public virtual void CheckPulse() {}

Если пользователь не аутентифицирован (токен истек), такой метод недоступен (защищен Authorizeатрибутом). Поскольку инфраструктура обрабатывает аутентификацию, а срок действия токена истекает, она добавляет 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");
    }
}

Наконец, на стороне клиента проверьте наличие такого пользовательского заголовка. Если присутствует - полное перенаправление на страницу входа в систему (в моем случае window.locationзаменяется URL-адрес из запроса, который обрабатывается моей платформой автоматически).

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;
                }
            }
    });
}
jwaliszko
источник
Я исправил проблему, используя событие PostAuthenticateRequest вместо события EndRequest.
Frinavale
@JaroslawWaliszko Я вставил неправильное событие в свой последний ответ! Я имел в виду событие PreSendRequestHeaders ..... не PostAuthenticateRequest! >> blush << спасибо, что указали на мою ошибку.
Фринавале
@JaroslawWaliszko при использовании WIF вы также можете вернуть 401 ответ на запросы AJAX и позволить вашему JavaScript обрабатывать их. Также вы предполагаете, что все 302 требуют аутентификации, которая может быть не во всех случаях. Я добавил ответ, если кому-то интересно.
Роб
26

Я думаю, что лучший способ справиться с этим - использовать существующие коды ответов протокола HTTP, в частности 401 Unauthorized.

Вот как я это решил:

  1. Сторона сервера: если сессия истекает, а запрос является ajax. отправить заголовок кода ответа 401
  2. Клиентская сторона: привязка к событиям ajax

    $('body').bind('ajaxSuccess',function(event,request,settings){
    if (401 == request.status){
        window.location = '/users/login';
    }
    }).bind('ajaxError',function(event,request,settings){
    if (401 == request.status){
        window.location = '/users/login';
    }
    });

IMO, это более общий вид, и вы не пишете новые пользовательские спецификации / заголовки. Вам также не нужно изменять какие-либо из ваших существующих вызовов ajax.

Редактировать: согласно комментарию @Rob ниже, 401 (код статуса HTTP для ошибок аутентификации) должен быть индикатором. Посмотрите 403 Запрещенных против 401 Несанкционированных HTTP-ответов для получения дополнительной информации. При этом некоторые веб-фреймворки используют 403 как для аутентификации, так и для ошибок авторизации - поэтому адаптируйтесь соответственно. Спасибо, Роб.

rynop
источник
Я использую тот же подход. Действительно ли jQuery вызывает ajaxSuccess для кода ошибки 403? Я думаю, что на самом деле нужна только часть ajaxError
Marius Balčytis
24

Я решил эту проблему следующим образом:

Добавьте промежуточное программное обеспечение для обработки ответа, если это перенаправление для запроса ajax, измените ответ на обычный ответ с URL-адресом перенаправления.

class AjaxRedirect(object):
  def process_response(self, request, response):
    if request.is_ajax():
      if type(response) == HttpResponseRedirect:
        r = HttpResponse(json.dumps({'redirect': response['Location']}))
        return r
    return response

Затем в ajaxComplete, если ответ содержит перенаправление, он должен быть перенаправлением, поэтому измените местоположение браузера.

$('body').ajaxComplete(function (e, xhr, settings) {
   if (xhr.status == 200) {
       var redirect = null;
       try {
           redirect = $.parseJSON(xhr.responseText).redirect;
           if (redirect) {
               window.location.href = redirect.replace(/\?.*$/, "?next=" + window.location.pathname);
           }
       } catch (e) {
           return;
       }
   }
}
Tyr
источник
20

У меня есть простое решение, которое работает для меня, без необходимости изменения кода сервера ... просто добавьте чайную ложку мускатного ореха ...

$(document).ready(function ()
{
    $(document).ajaxSend(
    function(event,request,settings)
    {
        var intercepted_success = settings.success;
        settings.success = function( a, b, c ) 
        {  
            if( request.responseText.indexOf( "<html>" ) > -1 )
                window.location = window.location;
            else
                intercepted_success( a, b, c );
        };
    });
});

Я проверяю наличие тега html, но вы можете изменить indexOf для поиска любой уникальной строки, существующей на вашей странице входа в систему ...

Timmerz
источник
Кажется, это не работает для меня, он продолжает вызывать функцию, определенную с помощью вызова ajax, как будто он не переопределяет метод успеха.
adriaanp
Кажется, не работает для меня, по крайней мере, больше. Возможное объяснение здесь: stackoverflow.com/a/12010724/260665
Радж Паван Гумдал
20

Другое решение, которое я нашел (особенно полезно, если вы хотите установить глобальное поведение), - это использовать $.ajaxsetup()метод вместе сstatusCode свойством . Как указали другие, не используйте код перенаправления status ( 3xx), вместо этого используйте 4xxкод состояния и обрабатывайте перенаправление на стороне клиента.

$.ajaxSetup({ 
  statusCode : {
    400 : function () {
      window.location = "/";
    }
  }
});

Замените 400на код состояния, который вы хотите обработать. Как уже упоминалось, 401 Unauthorizedможет быть хорошей идеей. Я использую, 400так как это очень неспецифично, и я могу использовать401 для более конкретных случаев (например, неверные учетные данные для входа). Таким образом, вместо прямого перенаправления ваш сервер должен возвращать 4xxкод ошибки, когда время сеанса истекло, и вы обрабатываете перенаправление на стороне клиента. Прекрасно работает для меня даже с такими фреймворками, как backbone.js

morten.c
источник
Где упомянуть функцию на странице?
Викрант
@Vikrant Если я правильно понимаю ваш вопрос, вы можете вызвать функцию сразу после загрузки jQuery и до выполнения ваших реальных запросов.
morten.c
19

Большинство данных решений используют обходной путь, используя дополнительный заголовок или неправильный HTTP-код. Эти решения, скорее всего, будут работать, но чувствуют себя немного «хаки». Я придумал другое решение.

Мы используем WIF, который настроен на перенаправление (passiveRedirectEnabled = "true") на ответ 401. Перенаправление полезно при обработке обычных запросов, но не будет работать для запросов AJAX (поскольку браузеры не будут выполнять перенаправление 302 /).

Используя следующий код в вашем global.asax, вы можете отключить перенаправление для запросов AJAX:

    void WSFederationAuthenticationModule_AuthorizationFailed(object sender, AuthorizationFailedEventArgs e)
    {
        string requestedWithHeader = HttpContext.Current.Request.Headers["X-Requested-With"];

        if (!string.IsNullOrEmpty(requestedWithHeader) && requestedWithHeader.Equals("XMLHttpRequest", StringComparison.OrdinalIgnoreCase))
        {
            e.RedirectToIdentityProvider = false;
        }
    }

Это позволяет вам возвращать 401 ответ на запросы AJAX, которые ваш javascript может затем обработать, перезагрузив страницу. При перезагрузке страницы выдается 401, который будет обрабатываться WIF (и WIF перенаправит пользователя на страницу входа).

Пример javascript для обработки ошибок 401:

$(document).ajaxError(function (event, jqxhr, settings, exception) {

    if (jqxhr.status == 401) { //Forbidden, go to login
        //Use a reload, WIF will redirect to Login
        location.reload(true);
    }
});
обкрадывать
источник
Хорошее решение Спасибо.
DanMan
19

Эта проблема может возникнуть при использовании метода RedirectToAction ASP.NET MVC. Чтобы форма не отображала ответ в div, вы можете просто использовать фильтр ответов ajax для входящих ответов с помощью $ .ajaxSetup . Если ответ содержит перенаправление MVC, вы можете оценить это выражение на стороне JS. Пример кода для JS ниже:

$.ajaxSetup({
    dataFilter: function (data, type) {
        if (data && typeof data == "string") {
            if (data.indexOf('window.location') > -1) {
                eval(data);
            }
        }
        return data;
    }
});

Если data: "window.location = '/ Acount / Login'", то фильтр выше поймает это и выполнит перенаправление вместо того, чтобы позволить отображению данных.

Пшемек Марцинкевич
источник
dataнаходится в теле ответа или заголовке?
GMsoF
16

Собрав воедино то, что Владимир Прудников и Томас Хансен сказали:

  • Измените код на стороне сервера, чтобы определить, является ли он XHR. Если это так, установите код ответа перенаправления на 278. В django:
   if request.is_ajax():
      response.status_code = 278

Это заставляет браузер воспринимать ответ как успешный и передает его в свой Javascript.

  • В вашем JS убедитесь, что отправка формы осуществляется через Ajax, проверьте код ответа и, при необходимости, перенаправьте:
$('#my-form').submit(function(event){ 

  event.preventDefault();   
  var options = {
    url: $(this).attr('action'),
    type: 'POST',
    complete: function(response, textStatus) {    
      if (response.status == 278) { 
        window.location = response.getResponseHeader('Location')
      }
      else { ... your code here ... } 
    },
    data: $(this).serialize(),   
  };   
  $.ajax(options); 
});
Грэм Кинг
источник
13
    <script>
    function showValues() {
        var str = $("form").serialize();
        $.post('loginUser.html', 
        str,
        function(responseText, responseStatus, responseXML){
            if(responseStatus=="success"){
                window.location= "adminIndex.html";
            }
        });     
    }
</script>
Приянка
источник
12

Пытаться

    $(document).ready(function () {
        if ($("#site").length > 0) {
            window.location = "<%= Url.Content("~") %>" + "Login/LogOn";
        }
    });

Поместите это на страницу входа. Если он был загружен в div на главной странице, он будет перенаправлять на страницу входа. «#site» - это идентификатор div, который находится на всех страницах, кроме страницы входа.

podeig
источник
12

Хотя ответы, кажется, работают для людей, если вы используете Spring Security, я обнаружил, что расширение LoginUrlAuthenticationEntryPoint и добавление специального кода для обработки AJAX более надежны. Большинство примеров перехватывает все перенаправления, а не только ошибки аутентификации. Это было нежелательно для проекта, над которым я работаю. Может также возникнуть необходимость расширить ExceptionTranslationFilter и переопределить метод «sendStartAuthentication», чтобы удалить шаг кэширования, если вы не хотите, чтобы неудавшийся AJAX-запрос кэшировался.

Пример AjaxAwareAuthenticationEntryPoint:

public class AjaxAwareAuthenticationEntryPoint extends
    LoginUrlAuthenticationEntryPoint {

    public AjaxAwareAuthenticationEntryPoint(String loginUrl) {
        super(loginUrl);
    }

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        if (isAjax(request)) {
            response.sendError(HttpStatus.UNAUTHORIZED.value(), "Please re-authenticate yourself");
        } else {
        super.commence(request, response, authException);
        }
    }

    public static boolean isAjax(HttpServletRequest request) {
        return request != null && "XMLHttpRequest".equals(request.getHeader("X-Requested-With"));
    }
}

Источники: 1 , 2

Джон
источник
3
Было бы полезно (для меня), если отрицательные избиратели объяснили бы, почему они отказываются голосовать. Если с этим решением есть что-то плохое, я бы хотел учиться на своих ошибках. Спасибо.
Джон
Если вы используете Spring, а также используете JSF, то также проверьте это: ("partal / ajax "). EqualsIgnoreCase (request.getHeader (" Face-Request "));
J Slick
Пользователи могли отказаться, потому что вы не упомянули: (1) необходимые моды на стороне клиента, чтобы обнаружить ваш ответ об ошибке; (2) необходимые моды для конфигурации Spring, чтобы добавить ваш настроенный LoginUrlAuthenticationEntryPoint в цепочку фильтров.
J Slick
Ваш ответ похож на этот ответ от @Arpad. Это сработало для меня; использование Spring Security 3.2.9. stackoverflow.com/a/8426947/4505142
Даррен Паркер
11

Я решил это, разместив следующее на моей странице login.php.

<script type="text/javascript">
    if (top.location.href.indexOf('login.php') == -1) {
        top.location.href = '/login.php';
    }
</script>
Пол Ричардс
источник
10

Позвольте мне еще раз процитировать проблему, описанную @Steg

У меня была похожая проблема с твоей. Я выполняю ajax-запрос, который имеет 2 возможных ответа: один перенаправляет браузер на новую страницу, а другой заменяет существующую HTML-форму на текущей странице новой.

ИМХО, это реальная проблема, и ее придется официально распространить на текущие стандарты HTTP.

Я полагаю, что новый стандарт Http будет использовать новый код состояния. значение: в настоящее время 301/302сообщает браузеру, что нужно перейти и получить содержимое этого запроса для нового location.

В расширенном стандарте будет сказано, что если ответ status: 308(только пример), то браузер должен перенаправить главную страницу на locationпредоставленную.

Что, как говорится; Я склонен уже подражать этому будущему поведению, и поэтому, когда нужен document.redirect, я отвечаю серверу:

status: 204 No Content
x-status: 308 Document Redirect
x-location: /login.html

Когда JS получает « status: 204», он проверяет наличие x-status: 308заголовка и выполняет document.redirect на страницу, указанную в locationзаголовке.

Это имеет какой-то смысл для вас?

Хаим Клар
источник
7

Некоторые могут найти следующее полезное:

Я хотел, чтобы клиенты перенаправлялись на страницу входа для любого действия по отдыху, отправляемого без токена авторизации. Поскольку все мои остальные действия основаны на Ajax, мне понадобился хороший общий способ перенаправления на страницу входа вместо обработки функции успеха Ajax.

Вот что я сделал:

На любой Ajax-запрос мой сервер будет возвращать ответ Json 200 «NEUT TO AUTHENTICATE» (если клиент должен пройти аутентификацию).

Простой пример на Java (на стороне сервера):

@Secured
@Provider
@Priority(Priorities.AUTHENTICATION)
public class AuthenticationFilter implements ContainerRequestFilter {

    private final Logger m_logger = LoggerFactory.getLogger(AuthenticationFilter.class);

    public static final String COOKIE_NAME = "token_cookie"; 

    @Override
    public void filter(ContainerRequestContext context) throws IOException {        
        // Check if it has a cookie.
        try {
            Map<String, Cookie> cookies = context.getCookies();

            if (!cookies.containsKey(COOKIE_NAME)) {
                m_logger.debug("No cookie set - redirect to login page");
                throw new AuthenticationException();
            }
        }
        catch (AuthenticationException e) {
            context.abortWith(Response.ok("\"NEED TO AUTHENTICATE\"").type("json/application").build());
        }
    }
}

В моем Javascript я добавил следующий код:

$.ajaxPrefilter(function(options, originalOptions, jqXHR) {
    var originalSuccess = options.success;

    options.success = function(data) {
        if (data == "NEED TO AUTHENTICATE") {
            window.location.replace("/login.html");
        }
        else {
            originalSuccess(data);
        }
    };      
});

И это все.

Томер
источник
5

в сервлете вы должны положить response.setStatus(response.SC_MOVED_PERMANENTLY); xmlHttp-статус «301», необходимый для перенаправления ...

и в функции $ .ajax вы не должны использовать .toString()функцию ..., просто

if (xmlHttp.status == 301) { top.location.href = 'xxxx.jsp'; }

проблема в том, что он не очень гибкий, вы не можете решить, куда хотите перенаправить ..

перенаправление через сервлеты должно быть лучшим способом. но я до сих пор не могу найти правильный способ сделать это.


источник
5

Я просто хотел фиксировать любые запросы AJAX для всей страницы. @SuperG заставил меня начать. Вот что я закончил:

// redirect ajax requests that are redirected, not found (404), or forbidden (403.)
$('body').bind('ajaxComplete', function(event,request,settings){
        switch(request.status) {
            case 301: case 404: case 403:                    
                window.location.replace("http://mysite.tld/login");
                break;
        }
});

Я хотел специально проверить определенные коды статуса http, чтобы основывать свое решение. Тем не менее, вы можете просто связаться с ajaxError, чтобы получить что-то кроме успеха (возможно, только 200?), Я мог бы просто написать:

$('body').bind('ajaxError', function(event,request,settings){
    window.location.replace("http://mysite.tld/login");
}
Bretticus
источник
1
последний будет скрывать любые другие ошибки, затрудняющие поиск и устранение неисправностей
Тим Абелл
1
403 не означает, что пользователь не аутентифицирован, это означает, что (возможно, аутентифицированный) пользователь не имеет разрешения на просмотр запрошенного ресурса. Так что не стоит перенаправлять на страницу входа
Роб
5

Если вы также хотите передать значения, вы также можете установить переменные сеанса и получить доступ, например: В вашем JSP вы можете написать

<% HttpSession ses = request.getSession(true);
   String temp=request.getAttribute("what_you_defined"); %>

И тогда вы можете сохранить это временное значение в переменной JavaScript и поиграть

karthik339
источник
5

У меня не было никакого успеха с решением заголовка - они никогда не были подобраны в моем методе ajaxSuccess / ajaxComplete. Я использовал ответ Стег с пользовательским ответом, но я немного изменил сторону JS. Я устанавливаю метод, который я вызываю в каждой функции, чтобы я мог использовать стандарт $.getи $.postметоды.

function handleAjaxResponse(data, callback) {
    //Try to convert and parse object
    try {
        if (jQuery.type(data) === "string") {
            data = jQuery.parseJSON(data);
        }
        if (data.error) {
            if (data.error == 'login') {
                window.location.reload();
                return;
            }
            else if (data.error.length > 0) {
                alert(data.error);
                return;
            }
        }
    }
    catch(ex) { }

    if (callback) {
        callback(data);
    }
}

Пример этого в использовании ...

function submitAjaxForm(form, url, action) {
    //Lock form
    form.find('.ajax-submit').hide();
    form.find('.loader').show();

    $.post(url, form.serialize(), function (d) {
        //Unlock form
        form.find('.ajax-submit').show();
        form.find('.loader').hide();

        handleAjaxResponse(d, function (data) {
            // ... more code for if auth passes ...
        });
    });
    return false;
}
jocull
источник
5

Наконец, я решаю проблему, добавив кастом HTTP Header. Непосредственно перед ответом на каждый запрос на стороне сервера я добавляю текущий запрошенный URL в заголовок ответа.

Мой тип приложения на сервере есть Asp.Net MVC, и у него есть хорошее место для этого. в Global.asaxI реализовано Application_EndRequestсобытие так:

    public class MvcApplication : System.Web.HttpApplication
    {

    //  ...
    //  ...

        protected void Application_EndRequest(object sender, EventArgs e)
        {
            var app = (HttpApplication)sender;
            app.Context.Response.Headers.Add("CurrentUrl",app.Context. Request.CurrentExecutionFilePath);
        }

    }

Это прекрасно работает для меня! Теперь в каждом ответе у JQuery $.postменя есть запрошенные, urlа также другие заголовки ответа, которые появляются в результате POSTметода по статусу 302,303 ....

и другая важная вещь заключается в том, что нет необходимости изменять код на стороне сервера или на стороне клиента.

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

Я написал это, может быть, кто-то поможет :)

Али Адлаваран
источник
4

У меня была эта проблема в приложении django, с которым я работаю (отказ от ответственности: я стараюсь учиться, и я ни в коем случае не эксперт). Я хотел использовать jQuery ajax для отправки запроса DELETE на ресурс, удалить его на стороне сервера, а затем отправить перенаправление обратно (в основном) на домашнюю страницу. Когда я отправлял HttpResponseRedirect('/the-redirect/')из скрипта python, метод jQuery ajax получал 200 вместо 302. Итак, я отправил ответ 300 с:

response = HttpResponse(status='300')
response['Location'] = '/the-redirect/' 
return  response

Затем я отправил / обработал запрос на клиенте с помощью jQuery.ajax так:

<button onclick="*the-jquery*">Delete</button>

where *the-jquery* =
$.ajax({ 
  type: 'DELETE', 
  url: '/resource-url/', 
  complete: function(jqxhr){ 
    window.location = jqxhr.getResponseHeader('Location'); 
  } 
});

Может быть, использование 300 не «правильно», но по крайней мере это сработало так, как я хотел.

PS: редактировать мобильную версию SO было огромной болью. Глупый провайдер отправил мой запрос на отмену сервиса прямо после того, как я закончил с моим ответом!

Бенни Джобиган
источник
4

Вы также можете перехватить отправку прототипа XMLHttpRequest. Это будет работать для всех посылок (jQuery / dojo / etc) с одним обработчиком.

Я написал этот код для обработки ошибки 500 страниц с истекшим сроком действия, но он должен работать так же хорошо, чтобы перехватить 200 перенаправления. Подготовьте запись в Википедии на XMLHttpRequest onreadystatechange о значении readyState.

// Hook XMLHttpRequest
var oldXMLHttpRequestSend = XMLHttpRequest.prototype.send;

XMLHttpRequest.prototype.send = function() {
  //console.dir( this );

  this.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 500 && this.responseText.indexOf("Expired") != -1) {
      try {
        document.documentElement.innerHTML = this.responseText;
      } catch(error) {
        // IE makes document.documentElement read only
        document.body.innerHTML = this.responseText;
      }
    }
  };

  oldXMLHttpRequestSend.apply(this, arguments);
}
Кертис Яллоп
источник
2

Кроме того, вы, вероятно, захотите перенаправить пользователя на указанный в заголовках URL-адрес. Итак, наконец, это будет выглядеть так:

$.ajax({
    //.... other definition
    complete:function(xmlHttp){
        if(xmlHttp.status.toString()[0]=='3'){
        top.location.href = xmlHttp.getResponseHeader('Location');
    }
});

UPD: Оппс. Есть та же задача, но она не работает. Заниматься этим Я покажу вам решение, когда найду его.

Владимир Прудников
источник
4
Этот ответ должен быть удален автором, поскольку он сам говорит, что это не работает. Он может опубликовать рабочее решение позже. -1
TheCrazyProgrammer
Идея хорошая, но проблема в том, что заголовок «Location» никогда не пропускается.
Мартин Зварик
Мое редактирование было отклонено, поэтому ... вы должны использовать "var xmlHttp = $ .ajax ({...." переменная не может быть внутри ... затем используйте: console.log (xmlHttp.getAllResponseHeaders ()) ;
Мартин Зварик
2

Я получил рабочий solulion , используя ответы из @John и @Arpad ссылки и @RobWinch ссылки

Я использую Spring Security 3.2.9 и jQuery 1.10.2.

Расширьте класс Spring, чтобы вызвать ответ 4XX только на запросы AJAX:

public class CustomLoginUrlAuthenticationEntryPoint extends LoginUrlAuthenticationEntryPoint {

    public CustomLoginUrlAuthenticationEntryPoint(final String loginFormUrl) {
        super(loginFormUrl);
    }

    // For AJAX requests for user that isn't logged in, need to return 403 status.
    // For normal requests, Spring does a (302) redirect to login.jsp which the browser handles normally.
    @Override
    public void commence(final HttpServletRequest request,
                         final HttpServletResponse response,
                         final AuthenticationException authException)
            throws IOException, ServletException {
        if ("XMLHttpRequest".equals(request.getHeader("X-Requested-With"))) {
            response.sendError(HttpServletResponse.SC_FORBIDDEN, "Access Denied");
        } else {
            super.commence(request, response, authException);
        }
    }
}

ApplicationContext-security.xml

  <security:http auto-config="false" use-expressions="true" entry-point-ref="customAuthEntryPoint" >
    <security:form-login login-page='/login.jsp' default-target-url='/index.jsp'                             
                         authentication-failure-url="/login.jsp?error=true"
                         />    
    <security:access-denied-handler error-page="/errorPage.jsp"/> 
    <security:logout logout-success-url="/login.jsp?logout" />
...
    <bean id="customAuthEntryPoint" class="com.myapp.utils.CustomLoginUrlAuthenticationEntryPoint" scope="singleton">
        <constructor-arg value="/login.jsp" />
    </bean>
...
<bean id="requestCache" class="org.springframework.security.web.savedrequest.HttpSessionRequestCache">
    <property name="requestMatcher">
      <bean class="org.springframework.security.web.util.matcher.NegatedRequestMatcher">
        <constructor-arg>
          <bean class="org.springframework.security.web.util.matcher.MediaTypeRequestMatcher">
            <constructor-arg>
              <bean class="org.springframework.web.accept.HeaderContentNegotiationStrategy"/>
            </constructor-arg>
            <constructor-arg value="#{T(org.springframework.http.MediaType).APPLICATION_JSON}"/>
            <property name="useEquals" value="true"/>
          </bean>
        </constructor-arg>
      </bean>
    </property>
</bean>

В моих JSP добавьте глобальный обработчик ошибок AJAX, как показано здесь

  $( document ).ajaxError(function( event, jqxhr, settings, thrownError ) {
      if ( jqxhr.status === 403 ) {
          window.location = "login.jsp";
      } else {
          if(thrownError != null) {
              alert(thrownError);
          } else {
              alert("error");
          }
      }
  });

Также удалите существующие обработчики ошибок из вызовов AJAX на страницах JSP:

        var str = $("#viewForm").serialize();
        $.ajax({
            url: "get_mongoDB_doc_versions.do",
            type: "post",
            data: str,
            cache: false,
            async: false,
            dataType: "json",
            success: function(data) { ... },
//            error: function (jqXHR, textStatus, errorStr) {
//                 if(textStatus != null)
//                     alert(textStatus);
//                 else if(errorStr != null)
//                     alert(errorStr);
//                 else
//                     alert("error");
//            }
        });

Я надеюсь, что это помогает другим.

Update1 Я обнаружил, что мне нужно добавить опцию (always-use-default-target = "true") в конфигурацию входа в систему. Это было необходимо, поскольку после того, как запрос AJAX перенаправляется на страницу входа в систему (из-за истекшего сеанса), Spring запоминает предыдущий запрос AJAX и автоматически перенаправляет его после входа в систему. Это приводит к отображению возвращенного JSON на странице браузера. Конечно, не то, что я хочу.

Update2 Вместо использования always-use-default-target="true", используйте пример @RobWinch блокировки AJAX-запросов от requstCache. Это позволяет обычным ссылкам перенаправляться на исходную цель после входа в систему, но AJAX переходит на домашнюю страницу после входа в систему.

Даррен Паркер
источник