Jak zarządzać żądaniem przekierowania po wywołaniu Ajax jQuery


1368

Używam $.post()do wywołania serwletu za pomocą Ajax, a następnie używam wynikowego fragmentu HTML do zastąpienia divelementu na bieżącej stronie użytkownika. Jeśli jednak upłynie limit czasu sesji, serwer wysyła dyrektywę przekierowania, aby wysłać użytkownika na stronę logowania. W tym przypadku jQuery zastępuje divelement zawartością strony logowania, zmuszając oczy użytkownika do zobaczenia naprawdę rzadkiej sceny.

Jak zarządzać dyrektywą przekierowującą z wywołania Ajax za pomocą jQuery 1.2.6?


1
(nie odpowiedź jako taka) - Robiłem to w przeszłości, edytując bibliotekę jquery i dodając stronę logowania na każdym ukończonym XHR. Nie jest to najlepsze rozwiązanie, ponieważ należy to robić przy każdej aktualizacji, ale rozwiązuje problem.
Sugendran

1
Zobacz powiązane pytanie: stackoverflow.com/questions/5941933/...
Nutel

HttpContext.Response.AddHeaderI czek na ajaxsetup sukcesu jest do zrobienia
LCJ

4
Dlaczego serwer nie może zwrócić 401? W takim przypadku możesz mieć globalny $ .ajaxSetup i użyć kodu stanu do przekierowania strony.
Vishal,

1
ten link doanduyhai.wordpress.com/2012/04/21/... daje mi właściwe rozwiązanie
pappu_kutty

Odpowiedzi:


697

Przeczytałem to pytanie i wdrożyłem podane podejście dotyczące ustawiania kodu statusu odpowiedzi HTTP na 278, aby uniknąć przezroczystej obsługi przekierowań przez przeglądarkę. Mimo, że to zadziałało, byłem trochę niezadowolony, ponieważ jest to trochę hack.

Po dalszych poszukiwaniach porzuciłem to podejście i użyłem JSON . W takim przypadku wszystkie odpowiedzi na żądania AJAX mają kod statusu 200, a treść odpowiedzi zawiera obiekt JSON zbudowany na serwerze. JavaScript na kliencie może następnie użyć obiektu JSON, aby zdecydować, co musi zrobić.

Miałem podobny problem do twojego. Wykonuję żądanie AJAX, które ma 2 możliwe odpowiedzi: jedną, która przekierowuje przeglądarkę na nową stronę i jedną, która zastępuje istniejący formularz HTML na bieżącej stronie nową. Aby to zrobić, kod jQuery wygląda mniej więcej tak:

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

Obiekt „dane” JSON jest konstruowany na serwerze i ma 2 członków: data.redirectoraz data.form. Uważam, że to podejście jest znacznie lepsze.


58
Jak stwierdzono w rozwiązaniu na stackoverflow.com/questions/503093/... lepiej jest użyć window.location.replace (data.redirect); niż window.location.href = data.redirect;
Carles Barrobés,

8
Jakikolwiek powód, dla którego nie byłoby lepiej używać kodów HTTP w akcji. Na przykład kod 307, który jest tymczasowym przekierowaniem HTTP?
Siergiej Golos

15
@Sergei Golos powodem jest to, że jeśli wykonasz przekierowanie HTTP, przekierowanie faktycznie nigdy nie dociera do wywołania zwrotnego ajax. Przeglądarka przetwarza przekierowanie dostarczając kod 200 z zawartością miejsca docelowego przekierowania.
Miguel Silva

2
jak wyglądałby kod serwera? mieć wartość zwracaną data.form i data.redirect. w zasadzie, jak ustalić, czy umieszczę w nim przekierowanie?
Vnge

6
Ta odpowiedź byłaby bardziej pomocna, gdyby pokazała, jak to się dzieje na serwerze.
Pedro Hoehl Carvalho

243

Rozwiązałem ten problem przez:

  1. Dodanie niestandardowego nagłówka do odpowiedzi:

    public ActionResult Index(){
        if (!HttpContext.User.Identity.IsAuthenticated)
        {
            HttpContext.Response.AddHeader("REQUIRES_AUTH","1");
        }
        return View();
    }
    
  2. Wiązanie funkcji JavaScript do ajaxSuccesszdarzenia i sprawdzenie, czy nagłówek istnieje:

    $(document).ajaxSuccess(function(event, request, settings) {
        if (request.getResponseHeader('REQUIRES_AUTH') === '1') {
           window.location = '/';
        }
    });
    

6
Co za niesamowite rozwiązanie. Podoba mi się pomysł kompleksowego rozwiązania. Muszę sprawdzić status 403, ale mogę w tym celu użyć powiązania ajaxSuccess na ciele (tego naprawdę szukałem). Dzięki.
Bretticus

4
Właśnie to zrobiłem i stwierdziłem, że potrzebuję ajaxComplete tam, gdzie korzystałem z funkcji $ .get () i żaden status inny niż 200 się nie uruchamiał. W rzeczywistości prawdopodobnie mógłbym po prostu związać się z ajaxError. Zobacz moją odpowiedź poniżej, aby uzyskać więcej informacji.
Bretticus

2
W wielu przypadkach jest w porządku, ale co, jeśli Twoja struktura obsługuje autoryzację?
jwaliszko

13
Podoba mi się podejście do nagłówka, ale również uważam - podobnie jak @ mwoods79 - że wiedza o tym, gdzie można przekierować, nie powinna się powielać. Rozwiązałem to, dodając nagłówek REDIRECT_LOCATION zamiast wartości logicznej.
rintcius

3
Pamiętaj, aby ustawić nagłówek odpowiedzi PO przekierowaniu. Jak opisano w innych odpowiedziach na tej stronie, przekierowanie może być przezroczyste dla procedury obsługi ajaxSucces. Tak więc umieściłem nagłówek w odpowiedzi GET strony logowania (która w końcu była i tylko wyzwalała ajaxSuccess w moim scenariuszu).
sieppl

118

Żadna przeglądarka nie obsługuje poprawnie odpowiedzi 301 i 302. W rzeczywistości standard mówi nawet, że powinni obchodzić się z nimi „w sposób przejrzysty”, co jest MASYWNYM bólem głowy dla dostawców Biblioteki Ajax. W Ra-Ajaxie byliśmy zmuszeni używać kodu statusu odpowiedzi HTTP 278 (tylko trochę „nieużywanego” kodu sukcesu) do obsługi przezroczystych przekierowań z serwera ...

To mnie naprawdę denerwuje, a jeśli ktoś tutaj ma jakieś „przyciąganie” w W3C, byłbym wdzięczny, że możesz poinformować W3C , że naprawdę musimy sami obsługiwać kody 301 i 302 ...! ;)


3
I na jeden ruch, że 278 powinno się oddzielić od oficjalnej specyfikacji HTTP.
Chris Marisic,

2
Czy nie obsługuje ich już w przejrzysty sposób? Jeśli zasób został przeniesiony, jego przejrzysta obsługa oznacza powtórzenie żądania pod podanym adresem URL. Tego oczekiwałbym za pomocą interfejsu API XMLHttpRequest.
Philippe Rathé,

@ PhilippeRathé Zgadzam się. Przejrzysta obsługa jest dokładnie tym, czego chcę. I nie wiem, dlaczego uważa się to za złe.
smwikipedia

@smwikipedia Aby zorganizować przekierowanie znaczników w sekcji głównej bez przekierowywania strony.
cmc

jeśli ktoś tutaj ma jakieś „przyciąganie” w W3C, byłbym wdzięczny za poinformowanie W3C, że naprawdę musimy sami obsługiwać kody 301 i 302…! ;)
Tommy.Tang

100

Rozwiązaniem, które ostatecznie zostało wdrożone, było użycie opakowania dla funkcji wywołania zwrotnego wywołania Ajax, aw tym opakowaniu sprawdzenie, czy istnieje określony element na zwróconej porcji HTML. Jeśli element został znaleziony, opakowanie wykonało przekierowanie. Jeśli nie, opakowanie przekazuje połączenie do faktycznej funkcji oddzwaniania.

Na przykład nasza funkcja otoki wyglądała mniej więcej tak:

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

Następnie, wykonując wywołanie Ajax, użyliśmy czegoś takiego:

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

To działało dla nas, ponieważ wszystkie wywołania Ajax zawsze zwracały HTML wewnątrz elementu DIV, którego używamy do zastąpienia fragmentu strony. Musieliśmy również przekierować na stronę logowania.


4
Zauważ, że można to skrócić do funkcji cbWrapper (funk) {return function (data) {if ($ ("# myForm", data) .size ()> 0) top.location.href = "login"; else funk (dane); }}. Potrzebujesz wtedy tylko cbWrapper (myActualCB) podczas dzwonienia do .post. Tak, kod w komentarzach to bałagan, ale należy zauważyć :)
Simen Echholt

rozmiar jest amortyzowany, więc możesz użyć .length tutaj zamiast size
sunil

66

Lubię metodę Timmerza z lekkim posmakiem cytryny. Jeśli kiedykolwiek wrócił contentType z text / html kiedy czekasz JSON , jesteś najprawdopodobniej przekierowany. W moim przypadku po prostu ponownie ładuję stronę i zostaje ona przekierowana na stronę logowania. Aha, i sprawdź, czy status jqXHR wynosi 200, co wydaje się głupie, ponieważ jesteś w funkcji błędu, prawda? W przeciwnym razie uzasadnione przypadki błędów wymuszą iteracyjne przeładowanie (ups)

$.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
dziękuję bardzo Brian, twoja odpowiedź była najlepsza dla mojego scenariusza, chociaż chciałbym, aby było bezpieczniejsze sprawdzenie, takie jak porównanie, do którego adresu URL / strony przekierowuje zamiast zwykłego sprawdzania typu zawartości. Nie mogłem znaleźć strony, do której następuje przekierowanie z obiektu jqXHR.
Johnny,

Sprawdziłem status 401, a następnie przekierowałem. Działa jak mistrz.
Eric

55

Użyj połączenia niskiego poziomu $.ajax():

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

Wypróbuj to dla przekierowania:

if (xmlHttp.code != 200) {
  top.location.href = '/some/other/page';
}

Starałem się unikać rzeczy niskiego poziomu. W każdym razie, załóżmy, że używam czegoś takiego, co opisujesz, jak wymusić przekierowanie przeglądarki po wykryciu, że kod HTTP to 3xx? Moim celem jest przekierowanie użytkownika, a nie tylko ogłoszenie, że jego sesja wygasła.
Elliot Vargas

4
Btw, $ .ajax () nie jest bardzo, bardzo niskim poziomem. Jest to tylko niski poziom pod względem jQuery, ponieważ jest $ .get, $ .post itp., Które są znacznie prostsze niż $ .ajax i wszystkie jego opcje.
Do

16
O chłopie! Przepraszam, że musiałem „odrzucić” twoją odpowiedź, to wciąż bardzo pomocne. Rzecz w tym, że przekierowaniami zarządza automatycznie XMLHttpRequest, dlatego ZAWSZE dostaję kod statusu 200 po przekierowaniu (westchnienie!). Myślę, że będę musiał zrobić coś paskudnego, jak analizowanie kodu HTML i poszukać znacznika.
Elliot Vargas

1
Ciekawe, czy sesja kończy się na serwerze, czy to nie znaczy, że serwer wysyła inny identyfikator SESSIONID? nie mogliśmy tego po prostu wykryć?
Salamander2007

10
UWAGA: Nie działa to w przypadku przekierowań. ajax przejdzie do nowej strony i zwróci jej kod statusu.

33

Chciałem tylko podzielić się moim podejściem, ponieważ może to pomóc komuś:

Zasadniczo załączyłem moduł JavaScript, który obsługuje uwierzytelnianie, takie jak wyświetlanie nazwy użytkownika, a także ta sprawa obsługująca przekierowanie na stronę logowania .

Mój scenariusz: Zasadniczo mamy serwer ISA, pomiędzy którym nasłuchuje wszystkich żądań i odpowiada 302 i nagłówkiem lokalizacji na naszej stronie logowania.

W moim module JavaScript moje początkowe podejście było podobne

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

Problem (jak już tu wspomniano) polega na tym, że przeglądarka sama obsługuje przekierowanie, dlatego moje ajaxCompletewywołanie zwrotne nigdy nie zostało wywołane, ale zamiast tego otrzymałem odpowiedź przekierowanej strony logowania, która oczywiście była status 200. Problem: jak wykryć, czy pomyślna odpowiedź 200 jest Twoją rzeczywistą stroną logowania, czy po prostu inną dowolną stroną?

Rozwiązanie

Ponieważ nie byłem w stanie przechwycić 302 odpowiedzi przekierowań, dodałem LoginPagenagłówek na mojej stronie logowania, który zawierał adres URL samej strony logowania. W module teraz słucham nagłówka i wykonuję przekierowanie:

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

... a to działa jak urok :). Możesz się zastanawiać, dlaczego umieszczam adres URL w LoginPagenagłówku ... cóż, zasadniczo dlatego, że nie znalazłem sposobu na określenie GETadresu URL wynikającego z automatycznego przekierowania lokalizacji z xhrobiektu ...


+1 - ale nagłówki niestandardowe powinny zaczynać się X-, więc lepiej byłoby użyć nagłówka X-LoginPage: http://example.com/login.
uınbɐɥs

6
@ShaquinTrifonoff Już nie. Nie użyłem przedrostka X-, ponieważ w czerwcu 2011 r. Dokument ITEF zaproponował jego wycofanie i rzeczywiście, w czerwcu 2012 r. Nie jest oficjalne, że nagłówki niestandardowe nie powinny być już przedrostkowane X-.
Juri

Mamy też serwer ISA i właśnie natknąłem się na ten sam problem. Zamiast obejść to w kodzie, skorzystaliśmy z instrukcji w kb2596444, aby skonfigurować ISA tak, aby przestał przekierowywać.
Scott Stone

29

Wiem, że ten temat jest stary, ale podam jeszcze jedno podejście, które znalazłem i poprzednio opisałem tutaj . Zasadniczo używam ASP.MVC z WIF (ale to nie jest tak naprawdę ważne w kontekście tego tematu - odpowiedź jest odpowiednia bez względu na to, jakie ramy są używane. Wskazówka pozostaje niezmieniona - radzenie sobie z problemami związanymi z błędami uwierzytelnienia podczas wykonywania żądań ajax ) .

Podejście pokazane poniżej może być zastosowane do wszystkich żądań ajax po wyjęciu z pudełka (jeśli nie przedefiniują one oczywiście przed wysyłaniem zdarzenia oczywiście).

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

Przed wykonaniem jakiegokolwiek żądania ajax CheckPulsewywoływana jest metoda (metoda kontrolera, która może być najprostsza):

[Authorize]
public virtual void CheckPulse() {}

Jeśli użytkownik nie jest uwierzytelniony (token wygasł), dostęp do takiej metody nie jest możliwy (chroniony przez Authorizeatrybut). Ponieważ środowisko obsługuje uwierzytelnianie, podczas gdy token wygasa, przekazuje on do odpowiedzi stan HTTP 302. Jeśli nie chcesz, aby twoja przeglądarka w sposób transparentny obsługiwała odpowiedź 302, złap ją w Global.asax i zmień status odpowiedzi - na przykład na 200 OK. Dodatkowo dodaj nagłówek, który instruuje Cię, aby przetworzyć taką odpowiedź w specjalny sposób (później po stronie klienta):

protected void Application_EndRequest()
{
    if (Context.Response.StatusCode == 302
        && (new HttpContextWrapper(Context)).Request.IsAjaxRequest())
    {                
        Context.Response.StatusCode = 200;
        Context.Response.AddHeader("REQUIRES_AUTH", "1");
    }
}

Wreszcie po stronie klienta sprawdź niestandardowy nagłówek. Jeśli jest obecny - należy dokonać pełnego przekierowania na stronę logowania (w moim przypadku window.locationzastępuje go adres URL z żądania, który jest obsługiwany automatycznie przez mój framework).

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

Rozwiązałem problem, używając zdarzenia PostAuthenticateRequest zamiast zdarzenia EndRequest.
Frinavale

@JaroslawWaliszko Wkleiłem niewłaściwe wydarzenie do mojej ostatniej odpowiedzi! Miałem na myśli zdarzenie PreSendRequestHeaders ..... nie PostAuthenticateRequest! >> rumieniec << dziękuję za zwrócenie uwagi na mój błąd.
Frinavale

@JaroslawWaliszko podczas korzystania z WIF możesz również zwrócić 401 odpowiedzi na żądania AJAX i pozwolić JavaScriptowi je obsłużyć. Zakładasz również, że wszystkie 302 wymagają uwierzytelnienia, co może nie być prawdziwe we wszystkich przypadkach. Dodałem odpowiedź, jeśli ktoś jest zainteresowany.
Rob

26

Myślę, że lepszym sposobem na poradzenie sobie z tym jest wykorzystanie istniejących kodów odpowiedzi protokołu HTTP 401 Unauthorized.

Oto jak to rozwiązałem:

  1. Po stronie serwera: jeśli sesja wygasa, a żądanie to ajax. wyślij nagłówek kodu odpowiedzi 401
  2. Po stronie klienta: powiązanie ze zdarzeniami 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 jest to bardziej ogólne i nie piszesz nowych niestandardowych specyfikacji / nagłówków. Nie powinieneś również modyfikować żadnych istniejących wywołań ajax.

Edycja: Według komentarza @ @ Rob poniżej 401 (kod stanu HTTP dla błędów uwierzytelnienia) powinien być wskaźnikiem. Aby uzyskać więcej informacji, zobacz 403 Zabronione vs 401 Nieautoryzowane odpowiedzi HTTP . Mając to na uwadze, niektóre frameworki internetowe używają 403 zarówno do błędów uwierzytelnienia, jak i autoryzacji - więc odpowiednio się dostosuj. Dzięki, Rob.


Używam tego samego podejścia. Czy jQuery naprawdę wywołuje ajaxSuccess na kod błędu 403? Myślę, że tak naprawdę potrzebna jest tylko część ajaxError
Marius Balčytis

24

Rozwiązałem ten problem w następujący sposób:

Dodaj oprogramowanie pośrednie, aby przetworzyć odpowiedź, jeśli jest to przekierowanie dla żądania ajax, zmień odpowiedź na normalną odpowiedź za pomocą adresu URL przekierowania.

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

Następnie w ajaxComplete, jeśli odpowiedź zawiera przekierowanie, musi to być przekierowanie, więc zmień lokalizację przeglądarki.

$('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

Mam proste rozwiązanie, które działa dla mnie, nie trzeba zmieniać kodu serwera ... wystarczy dodać łyżeczkę gałki muszkatołowej ...

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

Sprawdzam obecność tagu HTML, ale możesz zmienić indexOf, aby wyszukać unikalny ciąg znaków na stronie logowania ...


Wydaje mi się, że to nie działa, wciąż wywołuje funkcję zdefiniowaną przez wywołanie ajax, to tak, jakby nie przesłaniała metody sukcesu.
adriaanp

Wydaje mi się, że już nie działa. Możliwe wyjaśnienie tutaj: stackoverflow.com/a/12010724/260665
Raj Pawan Gumdal

20

Innym rozwiązaniem, które znalazłem (szczególnie przydatne, jeśli chcesz ustawić globalne zachowanie) jest użycie $.ajaxsetup()metody razem z statusCodewłaściwością . Jak zauważyli inni, nie używaj przekierowania statuscode ( 3xx), zamiast tego użyj 4xxkodu statusu i obsłuż przekierowanie po stronie klienta.

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

Zastąp 400kod statusu, który chcesz obsłużyć. Jak już wspomniano, 401 Unauthorizedmoże być dobrym pomysłem. Używam, 400ponieważ jest to bardzo niespecyficzne i mogę użyć 401do bardziej konkretnych przypadków (takich jak nieprawidłowe dane logowania). Zamiast przekierowywać bezpośrednio, backend powinien zwrócić 4xxkod błędu, gdy upłynie limit czasu sesji, a ty obsłużysz przekierowanie po stronie klienta. Działa idealnie dla mnie nawet z frameworkami takimi jak backbone.js


Gdzie wymienić funkcję na stronie?
Vikrant

@ Vikrant Jeśli dobrze rozumiem twoje pytanie, możesz wywołać tę funkcję zaraz po załadowaniu jQuery i przed wykonaniem rzeczywistych żądań.
morten.c

19

Większość podanych rozwiązań wykorzystuje obejście, dodatkowy nagłówek lub nieodpowiedni kod HTTP. Te rozwiązania najprawdopodobniej zadziałają, ale są nieco „hackerskie”. Wymyśliłem inne rozwiązanie.

Używamy WIF, który jest skonfigurowany do przekierowania (passiveRedirectEnabled = "true") w odpowiedzi 401. Przekierowanie jest przydatne podczas obsługi normalnych żądań, ale nie działa dla żądań AJAX (ponieważ przeglądarki nie wykonają przekierowania 302 /).

Za pomocą następującego kodu w pliku global.asax możesz wyłączyć przekierowanie dla żądań 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;
        }
    }

Umożliwia to zwrócenie 401 odpowiedzi na żądania AJAX, które następnie JavaScript może obsłużyć przez ponowne załadowanie strony. Przeładowanie strony wyrzuci 401, który będzie obsługiwany przez WIF (a WIF przekieruje użytkownika na stronę logowania).

Przykładowy javascript do obsługi błędów 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);
    }
});

Dobre rozwiązanie. Dzięki.
DanMan,

19

Ten problem może pojawić się wtedy przy użyciu metody RedirectToAction ASP.NET MVC. Aby zapobiec wyświetlaniu odpowiedzi w formularzu w formacie div, możesz po prostu zrobić filtr filtru odpowiedzi ajax dla przychodzących odpowiedzi za pomocą $ .ajaxSetup . Jeśli odpowiedź zawiera przekierowanie MVC, możesz ocenić to wyrażenie po stronie JS. Przykładowy kod dla JS poniżej:

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

Jeśli dane to: „window.location = '/ Acount / Login'” powyżej filtr złapie to i oceni, aby dokonać przekierowania zamiast pozwolić na wyświetlanie danych.


datajest w treści odpowiedzi lub nagłówku?
GMsoF,

16

Zestawiając to, co powiedzieli Władimir Prudnikow i Thomas Hansen:

  • Zmień kod po stronie serwera, aby wykryć, czy jest to XHR. Jeśli tak, ustaw kod odpowiedzi przekierowania na 278. W django:
   if request.is_ajax():
      response.status_code = 278

To sprawia, że ​​przeglądarka traktuje odpowiedź jako sukces i przekazuje ją skryptowi Javascript.

  • W swoim JS upewnij się, że formularz jest przesyłany przez Ajax, sprawdź kod odpowiedzi i przekieruj w razie potrzeby:
$('#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

Próbować

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

Umieść go na stronie logowania. Jeśli został załadowany do div na stronie głównej, przekieruje do strony logowania. „#site” to identyfikator div, który znajduje się na wszystkich stronach oprócz strony logowania.


12

Podczas gdy odpowiedzi wydają się działać dla ludzi, jeśli korzystasz z Spring Security, znalazłem rozszerzenie LoginUrlAuthenticationEntryPoint i dodanie konkretnego kodu do obsługi AJAX bardziej niezawodnego. Większość przykładów przechwytuje wszystkie przekierowania, a nie tylko błędy uwierzytelnienia. Było to niepożądane w przypadku projektu, nad którym pracuję. Może być konieczne rozszerzenie ExceptionTranslationFilter i zastąpienie metody „sendStartAuthentication” w celu usunięcia kroku buforowania, jeśli nie chcesz buforować nieudanego żądania AJAX.

Przykład 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"));
    }
}

Źródła: 1 , 2


3
Byłoby pomocne (dla mnie), gdyby głosujący w dół głosowali, dlaczego głosują w dół. Jeśli z tym rozwiązaniem jest coś złego, chciałbym nauczyć się na własnych błędach. Dzięki.
Jan

Jeśli używasz Springa, a także JSF, sprawdź również: („częściowe / ajax”). EqualsIgnoreCase (request.getHeader („faces-request”));
J Slick

Użytkownicy mogli głosować, ponieważ nie wspomniałeś: (1) o wymaganych modach po stronie klienta, aby wykryć twoją odpowiedź na błąd; (2) wymagane mody do konfiguracji Spring, aby dodać niestandardowy LoginUrlAuthenticationEntryPoint do łańcucha filtrów.
J Slick

Twoja odpowiedź jest podobna do tej z @Arpad. To zadziałało dla mnie; korzystanie z Spring Security 3.2.9. stackoverflow.com/a/8426947/4505142
Darren Parker

11

Rozwiązałem ten problem, umieszczając następujące informacje na mojej stronie login.php.

<script type="text/javascript">
    if (top.location.href.indexOf('login.php') == -1) {
        top.location.href = '/login.php';
    }
</script>

10

Pozwól, że przytoczę jeszcze raz problem opisany przez @Steg

Miałem podobny problem do twojego. Wykonuję żądanie ajax, które ma 2 możliwe odpowiedzi: jedną, która przekierowuje przeglądarkę na nową stronę i jedną, która zastępuje istniejący formularz HTML na bieżącej stronie nową.

IMHO to prawdziwe wyzwanie i będzie musiało zostać oficjalnie rozszerzone na obecne standardy HTTP.

Wierzę, że nowy standard HTTP będzie używał nowego kodu statusu. co oznacza: obecnie 301/302mówi przeglądarce, aby przejść i pobrać treść tego żądania do nowego location.

W rozszerzonym standardzie powie, że jeśli odpowiedź status: 308(tylko przykład), to przeglądarka powinna przekierować stronę główną do locationpodanego.

To powiedziawszy; Skłaniam się już do naśladowania tego przyszłego zachowania i dlatego, gdy potrzebny jest dokument document.redirect, serwer odpowiada:

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

Gdy JS otrzymuje „ status: 204”, sprawdza istnienie x-status: 308nagłówka i robi document.redirect do strony podanej w locationnagłówku.

Czy to ma dla ciebie jakiś sens?


7

Niektóre mogą okazać się przydatne poniżej:

Chciałem, aby klienci zostali przekierowani na stronę logowania w celu wykonania dowolnej akcji odpoczynku wysłanej bez tokena autoryzacji. Ponieważ wszystkie moje akcje odpoczynku są oparte na Ajaxie, potrzebowałem dobrego ogólnego sposobu przekierowania na stronę logowania zamiast obsługi funkcji sukcesu Ajax.

Oto co zrobiłem:

Na każde żądanie Ajax mój serwer zwróci odpowiedź Jsona 200 „POTRZEBUJĘ AUTENTENTACJI” (jeśli klient musi się uwierzytelnić).

Prosty przykład w Javie (po stronie serwera):

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

W moim Javascript dodałem następujący kod:

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

I o to chodzi.


5

w serwletu należy umieścić, response.setStatus(response.SC_MOVED_PERMANENTLY); aby wysłać status „301” xmlHttp potrzebny do przekierowania ...

a w funkcji $ .ajax nie powinieneś używać .toString()funkcji ..., tylko

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

problem polega na tym, że nie jest zbyt elastyczny, nie możesz zdecydować, gdzie chcesz przekierować.

przekierowywanie przez serwlety powinno być najlepszym sposobem. ale nadal nie mogę znaleźć właściwego sposobu, aby to zrobić.


5

Chciałem po prostu zatrzasnąć wszystkie żądania ajaxowe dla całej strony. @ Superup mnie zaczął. Oto, z czym skończyłem:

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

Chciałem szczególnie sprawdzić niektóre kody stanu HTTP, na których opiera się moja decyzja. Jednak możesz po prostu powiązać z ajaxError, aby uzyskać coś innego niż sukces (może tylko 200?) Mógłbym właśnie napisać:

$('body').bind('ajaxError', function(event,request,settings){
    window.location.replace("http://mysite.tld/login");
}

1
ten ostatni ukryłby wszelkie inne błędy powodujące problemy z rozwiązywaniem problemów
Tim Abell,

1
403 nie oznacza, że ​​użytkownik nie jest uwierzytelniony, oznacza to, że (prawdopodobnie uwierzytelniony) użytkownik nie ma uprawnień do przeglądania żądanego zasobu. Dlatego nie powinno przekierowywać na stronę logowania
Rob

5

Jeśli chcesz również przekazać wartości, możesz także ustawić zmienne sesji i uzyskać dostęp np. W: JSP możesz pisać

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

Następnie możesz zapisać tę wartość temp w zmiennej javascript i bawić się


5

Nie miałem żadnego sukcesu z rozwiązaniem nagłówkowym - nigdy nie zostały one odebrane w mojej metodzie ajaxSuccess / ajaxComplete. Użyłem odpowiedzi Stega z niestandardową odpowiedzią, ale trochę zmodyfikowałem stronę JS. Ustawiam metodę, którą wywołuję w każdej funkcji, dzięki czemu mogę używać standardu $.geti $.postmetod.

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

Przykład użycia ...

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

Wreszcie rozwiązuję problem, dodając niestandardowy HTTP Header. Tuż przed odpowiedzią na każde żądanie po stronie serwera dodaję bieżący żądany adres URL do nagłówka odpowiedzi.

Mój typ aplikacji na serwerze jest Asp.Net MVCi jest na to dobre miejsce. w Global.asaxI wdrożone Application_EndRequestzdarzenie tak:

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

    }

Działa idealnie dla mnie! Teraz w każdej odpowiedzi z JQuery $.postmam żądanych url, a także inne nagłówki odpowiedzi, które przychodzi jako skutek POSTsposobu według statusu 302, 303....

a inną ważną rzeczą jest to, że nie ma potrzeby modyfikowania kodu po stronie serwera ani po stronie klienta.

a następną jest możliwość uzyskania dostępu do innych informacji dotyczących akcji post, takich jak błędy, wiadomości i ..., w ten sposób.

Wysłałem to, może komuś pomóc :)


4

Miałem ten problem w aplikacji django, którą majstruję (zastrzeżenie: majstruję, aby się uczyć i nie jestem w żaden sposób ekspertem). Chciałem użyć jQuery ajax, aby wysłać żądanie DELETE do zasobu, usunąć je po stronie serwera, a następnie wysłać przekierowanie z powrotem (w zasadzie) do strony głównej. Kiedy wysłałem HttpResponseRedirect('/the-redirect/')ze skryptu Pythona, metoda ajax jQuery'ego otrzymywała 200 zamiast 302. Tak więc, wysłałem odpowiedź 300 z:

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

Następnie wysłałem / obsłużyłem żądanie na kliencie z jQuery.ajax w następujący sposób:

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

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

Może używanie 300 nie jest „właściwe”, ale przynajmniej działało tak, jak chciałem.

PS: edytowanie mobilnej wersji SO było bardzo trudne. Głupi usługodawca internetowy złożył moje żądanie anulowania usługi, kiedy skończyłem z moją odpowiedzią!


4

Możesz także podpiąć prototyp wysyłania XMLHttpRequest. Działa to dla wszystkich wysyłek (jQuery / dojo / etc) z jednym modułem obsługi.

Napisałem ten kod, aby obsłużyć błąd 500 stron, który wygasł, ale powinien działać równie dobrze, aby przechwycić przekierowanie 200. Przygotuj wpis w Wikipedii na XMLHttpRequest onreadystatzmień o znaczeniu 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

Dodatkowo prawdopodobnie będziesz chciał przekierować użytkownika do podanego w nagłówku adresu URL. W końcu będzie to wyglądać tak:

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

UPD: Opps. Miej to samo zadanie, ale to nie działa. Robienie tego. Pokażę ci rozwiązanie, kiedy je znajdę.


4
Autor powinien usunąć tę odpowiedź, ponieważ sam twierdzi, że to nie działa. Później może opublikować działające rozwiązanie. -1
TheCrazyProgrammer

Pomysł jest dobry, ale problem polega na tym, że nagłówek „Lokalizacja” nigdy nie jest przekazywany.
Martin Zvarík,

Moja edycja została odrzucona, więc ... musisz użyć "var xmlHttp = $ .ajax ({...." zmienna nie może być wewnątrz ... a następnie użyj: console.log (xmlHttp.getAllResponseHeaders ()) ;
Martin Zvarík,

2

Mam solulion roboczą używając odpowiedzi od @John i @Arpad linku i @RobWinch linku

Używam Spring Security 3.2.9 i jQuery 1.10.2.

Rozszerz klasę Spring, aby powodować odpowiedź 4XX tylko z żądań 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>

Do moich stron JSP dodaj globalną procedurę obsługi błędów AJAX, jak pokazano tutaj

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

Usuń także istniejące procedury obsługi błędów z wywołań AJAX na stronach 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");
//            }
        });

Mam nadzieję, że to pomaga innym.

Aktualizacja 1 Odkryłem , że muszę dodać opcję (always-use-default-target = "true") do konfiguracji formularza logowania. Było to potrzebne, ponieważ po przekierowaniu żądania AJAX do strony logowania (z powodu wygasłej sesji) Spring zapamiętuje poprzednie żądanie AJAX i automatycznie przekierowuje je po zalogowaniu. Powoduje to, że zwrócony JSON jest wyświetlany na stronie przeglądarki. Oczywiście nie to, czego chcę.

Update2 Zamiast używać always-use-default-target="true", użyj przykładu @RobWinch blokowania żądań AJAX z requstCache. Umożliwia to przekierowanie zwykłych linków do ich pierwotnego celu po zalogowaniu, ale AJAX przechodzi do strony głównej po zalogowaniu.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.