Як керувати запитом на переадресацію після виклику jQuery Ajax


1368

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

Як я можу керувати директивою про переадресацію з виклику Ajax за допомогою jQuery 1.2.6?


1
(не відповідь як така) - я це робив у минулому, редагуючи бібліотеку jquery і додаючи чек на сторінку входу в кожний комплект XHR. Не найкраще рішення, тому що це потрібно робити кожного разу при оновленні, але це вирішує проблему.
Сугендран

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. Я знайшов цей підхід набагато кращим.


58
Як зазначено в рішенні в stackoverflow.com/questions/503093/… , краще використовувати window.location.replace (data.redirect); ніж window.location.href = data.redirect;
Carles Barrobés

8
Будь-яка причина, чому це було б не краще використовувати HTTP коди в дії. Наприклад, код 307, який є тимчасовим перенаправленням HTTP?
Сергій Голос

15
@Sergei Golos, причина полягає в тому, що якщо ви переспрямовуєте HTTP, перенаправлення насправді ніколи не надходить до зворотного виклику успіху в ajax. Браузер обробляє переадресацію, доставляючи код 200 із вмістом пункту призначення.
Мігель Сільва

2
як виглядатиме код сервера? мати повернене значення data.form та data.redirect. в основному, як я можу визначити, чи покласти в нього переспрямування?
Vnge

6
Ця відповідь була б кориснішою, якби вона показала, як це робиться на сервері.
Педро Гель Карвальо

243

Я вирішив це питання:

  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 = '/';
        }
    });

6
Яке дивовижне рішення. Мені подобається ідея єдиного рішення. Мені потрібно перевірити стан 403, але я можу використовувати для цього прив'язку ajaxSuccess на тілі (саме це я і справді шукав.) Спасибі
Bretticus

4
Я щойно це зробив і виявив, що мені потрібен ajaxComplete, де я використовував функцію $ .get (), і будь-який статус, окрім 200, не запускав. Насправді, я міг, можливо, просто прив’язаний до ajaxError замість цього. Дивіться мою відповідь нижче для отримання більш детальної інформації.
Bretticus

2
У багатьох випадках це нормально, але що робити, якщо ваша структура обробляє авторизацію?
jwaliszko

13
Мені подобається підхід до заголовка, але я також думаю - як @ mwoods79 - що знання, куди слід перенаправлятись, не слід дублювати. Я вирішив це, додавши заголовок REDIRECT_LOCATION замість булевого.
rintcius

3
Не забудьте встановити заголовок відповіді ПІСЛЯ переадресації. Як описано в інших відповідях на цій сторінці, перенаправлення може бути прозорим для обробника ajaxSucces. Таким чином, я включив заголовок на GET-відповідь сторінки входу (що врешті-решт і викликало лише ajaxSuccess у моєму сценарії).
sieppl

118

Жоден браузер не обробляє правильно відповіді 301 та 302. Насправді стандарт навіть говорить, що вони повинні поводитися з ними «прозоро», що є МАСИВНИМ головним болем для постачальників бібліотеки Ajax. У Ra-Ajax нас змусили використовувати код статусу відповіді HTTP 278 (лише якийсь "невикористаний" код успіху), щоб прозоро обробляти переадресації з сервера ...

Це мене справді дратує, і якщо хтось тут щось "потягне" у W3C, я би вдячний, що ви можете дозволити W3C знати, що нам дійсно потрібно обробляти коди 301 та 302 самі ...! ;)


3
Я вважаю, що 278 має стати окремим офіційним HTTP Spec.
Кріс Марісіч

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) {функція повернення (дані) {if ($ ("# myForm", дані) .size ()> 0) top.location.href = "вхід"; else function (дані); }}. Тоді вам потрібен лише cbWrapper (myActualCB) лише при виклику .post. Так, код у коментарях - це безлад, але це слід зазначити :)
Simen Echholt

розмір амортизується, тому ви можете використовувати .length тут замість розміру
sunil

66

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

$.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();
    }

});

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
Btw, $ .ajax () не дуже, дуже низький рівень. Це просто низький рівень з точки зору jQuery, оскільки є $ .get, $ .post тощо, які набагато простіші за $ .ajax та всі його параметри.
До

16
О, малюк! Вибачте, що довелося "не прийняти" вашу відповідь, це все ще дуже корисно. Справа в тому, що перенаправленнями автоматично керує XMLHttpRequest, отже, Я ВЖЕ отримую код 200 після перенаправлення (зітхання!). Я думаю, що мені доведеться зробити щось неприємне, як розбирати HTML і шукати маркер.
Елліот Варгас

1
Цікаво, що якщо сеанс закінчується на сервері, чи це не означає, що сервер надсилає інший SESSIONID? ми не могли це просто виявити?
Саламандер2007

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об'єкта ...


+1 - але користувацькі заголовки повинні починатися з цього X-, тому краще використовувати заголовок X-LoginPage: http://example.com/login.
uınbɐɥs

6
@ShaquinTrifonoff Більше. Я не використовував префікс X, тому що в червні 2011 року документ ITEF запропонував їх анулювання, і справді, з червня 2012 року, це не офіційна інформація про те, що користувацькі заголовки більше не повинні префіксуватися X-.
Юрі

У нас також є сервер ISA, і я просто зіткнувся з тим же питанням. Замість того, щоб працювати над цим кодом, ми використовували інструкції в kb2596444 для налаштування ISA для припинення переадресації.
scott stone

29

Я знаю, що ця тема давня, але я наведу ще один підхід, який я знайшов і описав тут . В основному я використовую ASP.MVC з WIF (але це не дуже важливо для контексту цієї теми - відповідь є адекватною незалежно від того, які рамки використовуються. Підказки залишаються незмінними - вирішуючи проблеми, пов’язані з помилками аутентифікації під час виконання запитів ajax ) .

Підхід, показаний нижче, може бути застосований до всіх запитів ajax поза коробкою (якщо вони очевидно не переосмислюються раніше, ніж посилати).

$.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 ОК. Крім того, додайте заголовок, який доручає спеціально обробити таку відповідь (пізніше на стороні клієнта):

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;
                }
            }
    });
}

Я вирішив проблему, використовуючи подію PostAuthenticateRequest замість події EndRequest.
Frinavale

@JaroslawWaliszko Я вклеював неправильну подію у свою останню відповідь! Я мав на увазі подію PreSendRequestHeaders ..... не PostAuthenticateRequest! >> червоніти << дякую за те, що ти вказав на мою помилку.
Frinavale

@JaroslawWaliszko, використовуючи WIF, ви також можете повернути 401 відповідь на запити AJAX і дозволити вашому JavaScript обробляти їх. Також ви припускаєте, що всі 302 потребують автентифікації, що може бути неправдивим у всіх випадках. Я додав відповідь, якщо когось цікавить.
Роб

26

Я думаю, що кращим способом вирішити це є використання існуючих кодів відповідей протоколу, зокрема 401 Unauthorized.

Ось як я це вирішив:

  1. Сторона сервера: Якщо сеанс закінчується, а запит не працює. відправити заголовок коду відповіді 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.

Редагувати: коментар Per @ Rob нижче, 401 (код статусу HTTP для помилок аутентифікації) повинен бути індикатором. Докладніші відомості див. У розділі 403 Заборонено проти 401 Несанкціоновані відповіді HTTP . Зважаючи на це, деякі веб-рамки використовують 403 як для аутентифікації, так і для помилок авторизації - тому адаптуйтеся відповідно. Дякую Роб.


Я використовую той самий підхід. Чи дійсно 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;
       }
   }
}

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 для пошуку будь-якої унікальної рядки на вашій сторінці входу ...


Це, здається, не працює для мене, він продовжує викликати функцію, визначену викликом ajax, це як би не перекриває метод успіху.
adriaanp

Здається, це не працює для мене, принаймні більше. Можливе пояснення: stackoverflow.com/a/12010724/260665
Радж Паван Gumdal

20

Ще одне знайдене нами рішення (особливо корисне, якщо ви хочете встановити глобальну поведінку) - це використовувати $.ajaxsetup()метод разом із statusCodeвластивістю . Як зазначали інші, не використовуйте код перенаправлення ( 3xx), а скористайтеся 4xxкодом статусу та обробляйте клієнтську сторону переадресації.

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

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


Де згадати функцію на сторінці?
Вікрант

@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

Ця проблема може з’явитися тоді, використовуючи метод ASP.NET MVC RedirectToAction. Щоб запобігти відображенню форми у відповіді у 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;
    }
});

Якщо дані є: "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";
        }
    });

Розмістіть його на сторінці входу. Якщо він був завантажений у діві на головній сторінці, він буде перенаправлений на сторінку входу. "#site" - це ідентифікатор div, який розміщений на всіх сторінках, крім сторінки входу.


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, то також перевірте це: ("частковий / ajax"). EqualsIgnoreCase (request.getHeader ("обличчя-запит"));
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 на поточній сторінці новою.

IMHO - це справжній виклик, і його доведеться офіційно розширити до діючих стандартів 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 "НЕОБХІДНО АВТЕНТИКАТИВАТИ" (якщо клієнту потрібно пройти автентифікацію).

Простий приклад на 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); щоб надіслати "301" xmlHttp статус, який вам потрібен для перенаправлення ...

і у функції $ .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");
}

1
останній ховав би будь-які інші помилки, що робили проблему проблемою
Тім Абел

1
403 не означає, що користувач не має автентифікації, це означає, що (ймовірно, автентифікований) користувач не має дозволу на перегляд запитуваного ресурсу. Тож не слід перенаправляти на сторінку входу
Роб

5

Якщо ви також хочете передати значення, ви також можете встановити змінні сеансу та отримати доступ до Напр.: У своєму jsp ви можете написати

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

І тоді ви можете зберігати це значення temp у вашій змінній javascript і грати


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;
}

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

Я мав цю проблему в додатку джанго, з яким я замислювався (відмова: я намагаюся вчитися, і я жодним чином не є експертом). Що я хотів зробити, це використовувати jQuery ajax, щоб надіслати DELETE запит на ресурс, видалити його на стороні сервера, а потім надіслати перенаправлення назад на (в основному) на головну сторінку. Коли я надсилав HttpResponseRedirect('/the-redirect/')із сценарію python, метод ajax jQuery отримував 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 / тощо) з одним обробником.

Я написав цей код, щоб обробити помилку, що втратила чинність на 500 сторінок, але він повинен працювати так само добре, як уловлювати переадресацію на 200. Підготуйте запис вікіпедії на XMLHttpRequest вже вжезмініть інформацію про значення 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

Ідея хороша, але проблема полягає в тому, що заголовок "Місцезнаходження" ніколи не передається.
Мартін Зварик

Мою редакцію було відхилено, тому ... ви повинні використовувати "var xmlHttp = $ .ajax ({...." змінна не може бути всередині ... потім використовувати: console.log (xmlHttp.getAllResponseHeaders ()) ;
Мартін Зварик

2

Я отримав робоче рішення, використовуючи відповіді за посиланням @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 Я виявив, що мені потрібно додати параметр (завжди використовувати-default-target = "true") у конфігурацію форми входу у форму. Це було потрібно, оскільки після того, як запит AJAX буде перенаправлений на сторінку входу (через закінчений термін сеансу), Spring запам'ятовує попередній запит AJAX і автоматично перенаправляє на нього після входу. Це призводить до відображення повернутого JSON на сторінці браузера. Звичайно, не те, що я хочу.

Update2 Замість використання always-use-default-target="true"використовуйте @RobWinch приклад блокування запитів AJAX з requstCache. Це дає змогу звичайні посилання після входу переадресовувати їх початкову ціль, але AJAX після входу перейти на головну сторінку.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.