So verwalten Sie eine Umleitungsanforderung nach einem jQuery Ajax-Aufruf


1369

Ich verwende $.post(), um ein Servlet mit Ajax aufzurufen und dann das resultierende HTML-Fragment zu verwenden, um ein divElement auf der aktuellen Seite des Benutzers zu ersetzen . Wenn die Sitzung jedoch abgelaufen ist, sendet der Server eine Umleitungsanweisung, um den Benutzer zur Anmeldeseite zu senden. In diesem Fall ersetzt jQuery das divElement durch den Inhalt der Anmeldeseite und zwingt die Augen des Benutzers, tatsächlich eine seltene Szene zu sehen.

Wie kann ich eine Umleitungsanweisung von einem Ajax-Aufruf mit jQuery 1.2.6 verwalten?


1
(keine Antwort als solche) - Ich habe dies in der Vergangenheit getan, indem ich die JQuery-Bibliothek bearbeitet und bei jedem XHR-Abschluss einen Check für die Anmeldeseite hinzugefügt habe. Nicht die beste Lösung, da dies bei jedem Upgrade durchgeführt werden müsste, aber das Problem wird dadurch gelöst.
Sugendran


Der HttpContext.Response.AddHeaderund Check bei Ajaxsetup Erfolg ist der richtige Weg
LCJ

4
Warum kann der Server 401 nicht zurückgeben? In diesem Fall können Sie ein globales $ .ajaxSetup haben und den Statuscode verwenden, um die Seite umzuleiten.
Vishal

1
Dieser Link doanduyhai.wordpress.com/2012/04/21/… gibt mir die richtige Lösung
pappu_kutty

Antworten:


697

Ich habe diese Frage gelesen und den Ansatz implementiert, der in Bezug auf das Setzen des Antwort- HTTP-Statuscodes auf 278 angegeben wurde, um zu vermeiden, dass der Browser die Weiterleitungen transparent behandelt. Obwohl dies funktionierte, war ich ein wenig unzufrieden, da es ein bisschen ein Hack ist.

Nachdem ich mich weiter umgesehen hatte, gab ich diesen Ansatz auf und verwendete JSON . In diesem Fall haben alle Antworten auf AJAX-Anforderungen den Statuscode 200 und der Hauptteil der Antwort enthält ein JSON-Objekt, das auf dem Server erstellt wird. Das JavaScript auf dem Client kann dann anhand des JSON-Objekts entscheiden, was zu tun ist.

Ich hatte ein ähnliches Problem wie Sie. Ich führe eine AJAX-Anfrage mit zwei möglichen Antworten aus: eine, die den Browser auf eine neue Seite umleitet, und eine, die ein vorhandenes HTML-Formular auf der aktuellen Seite durch ein neues ersetzt. Der dazu erforderliche jQuery-Code sieht ungefähr so ​​aus:

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

Das JSON-Objekt "data" ist auf dem Server so aufgebaut, dass es zwei Mitglieder hat: data.redirectund data.form. Ich fand diesen Ansatz viel besser.


58
Wie in der Lösung unter stackoverflow.com/questions/503093/… angegeben , ist es besser, window.location.replace (data.redirect) zu verwenden. als window.location.href = data.redirect;
Carles Barrobés

8
Jeder Grund, warum es nicht besser wäre, HTTP-Codes für die Aktion zu verwenden. Zum Beispiel ein 307-Code, der HTTP Temporary Redirect ist?
Sergei Golos

15
@ Sergei Golos Der Grund dafür ist, dass bei einer HTTP-Umleitung die Umleitung tatsächlich nie zum Ajax-Erfolgsrückruf gelangt. Der Browser verarbeitet die Umleitung und liefert einen 200-Code mit dem Inhalt des Umleitungsziels.
Miguel Silva

2
Wie würde der Servercode aussehen? einen Rückgabewert von data.form und data.redirect haben. Wie bestimme ich im Grunde, ob ich eine Weiterleitung eingefügt habe?
Vnge

6
Diese Antwort wäre hilfreicher, wenn sie gezeigt hätte, wie dies auf dem Server gemacht wird.
Pedro Hoehl Carvalho

243

Ich habe dieses Problem gelöst durch:

  1. Hinzufügen eines benutzerdefinierten Headers zur Antwort:

    public ActionResult Index(){
        if (!HttpContext.User.Identity.IsAuthenticated)
        {
            HttpContext.Response.AddHeader("REQUIRES_AUTH","1");
        }
        return View();
    }
  2. Binden einer JavaScript-Funktion an das ajaxSuccessEreignis und Überprüfen, ob der Header vorhanden ist:

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

6
Was für eine großartige Lösung. Ich mag die Idee einer Lösung aus einer Hand. Ich muss nach einem 403-Status suchen, aber ich kann dafür die ajaxSuccess-Bindung am Körper verwenden (was ich wirklich gesucht habe). Danke.
Bretticus

4
Ich habe dies gerade getan und festgestellt, dass ich ajaxComplete brauchte, wo ich die Funktion $ .get () verwendete und ein anderer Status als 200 nicht ausgelöst wurde. Tatsächlich hätte ich wahrscheinlich stattdessen einfach an ajaxError binden können. Siehe meine Antwort unten für weitere Details.
Bretticus

2
In vielen Fällen ist dies in Ordnung, aber was ist, wenn Ihr Framework die Autorisierung übernimmt?
Jwaliszko

13
Ich mag den Header-Ansatz, denke aber auch - wie @ mwoods79 -, dass das Wissen darüber, wohin umgeleitet werden soll, nicht dupliziert werden sollte. Ich habe das gelöst, indem ich einen Header REDIRECT_LOCATION anstelle eines Booleschen Werts hinzugefügt habe.
Rintcius

3
Achten Sie darauf, den Header für die Antwort NACH der Umleitung festzulegen. Wie in anderen Antworten auf dieser Seite beschrieben, ist die Umleitung möglicherweise für den AjaxSucces-Handler transparent. Daher habe ich den Header in die GET-Antwort der Anmeldeseite aufgenommen (was schließlich und nur in meinem Szenario ajaxSuccess auslöste).
Sieppl

118

Kein Browser verarbeitet 301- und 302-Antworten korrekt. Tatsächlich sagt der Standard sogar, dass sie "transparent" damit umgehen sollten, was für Anbieter von Ajax Library ein MASSIVES Problem darstellt. In Ra-Ajax mussten wir den HTTP-Antwortstatuscode 278 (nur einen "nicht verwendeten" Erfolgscode) verwenden, um transparente Weiterleitungen vom Server zu verarbeiten ...

Das ärgert mich wirklich, und wenn jemand hier etwas "Pull" in W3C hat, würde ich es begrüßen, wenn Sie W3C wissen lassen könnten , dass wir wirklich 301- und 302-Codes selbst verarbeiten müssen ...! ;)


3
Ich für einen Schritt, dass 278 von der offiziellen HTTP-Spezifikation getrennt werden sollte.
Chris Marisic

2
Behandelt es sie nicht schon transparent? Wenn eine Ressource verschoben wurde, bedeutet eine transparente Behandlung, dass die Anforderung unter der angegebenen URL wiederholt wird. Das würde ich mit der XMLHttpRequest-API erwarten.
Philippe Rathé

@ PhilippeRathé Zustimmen. Transparente Handhabung ist genau das, was ich will. Und ich weiß nicht, warum es als schlecht angesehen wird.
Smwikipedia

@smwikipedia Um eine Weiterleitung des Markups im Hauptabschnitt zu veranlassen, ohne die Seite umzuleiten.
cmc

Wenn jemand hier ein "Pull" in W3C hat, würde ich es begrüßen, wenn Sie W3C wissen lassen könnten, dass wir die Codes 301 und 302 wirklich selbst verarbeiten müssen ...! ;)
Tommy.Tang

100

Die Lösung, die schließlich implementiert wurde, bestand darin, einen Wrapper für die Rückruffunktion des Ajax-Aufrufs zu verwenden und in diesem Wrapper zu prüfen, ob ein bestimmtes Element im zurückgegebenen HTML-Block vorhanden ist. Wenn das Element gefunden wurde, führte der Wrapper eine Umleitung aus. Wenn nicht, leitete der Wrapper den Anruf an die eigentliche Rückruffunktion weiter.

Zum Beispiel war unsere Wrapper-Funktion so etwas wie:

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

Beim Ajax-Anruf haben wir dann Folgendes verwendet:

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

Dies funktionierte für uns, da alle Ajax-Aufrufe immer HTML in einem DIV-Element zurückgaben, mit dem wir einen Teil der Seite ersetzen. Außerdem mussten wir nur zur Anmeldeseite umleiten.


4
Beachten Sie, dass dies auf die Funktion cbWrapper (Funktion) {Rückgabefunktion (Daten) {if ($ ("# myForm", Daten) .size ()> 0) top.location.href = "login" gekürzt werden kann; sonst Funktion (Daten); }}. Sie benötigen dann nur cbWrapper (myActualCB), wenn Sie .post aufrufen. Ja, Code in Kommentaren ist ein Chaos, aber es sollte beachtet werden :)
Simen Echholt

Größe wird abgeschrieben, so dass Sie .length hier anstelle von Größe verwenden können
sunil

66

Ich mag Timmerz 'Methode mit einem leichten Schuss Zitrone. Wenn Sie jemals ContentType von Text / HTML zurückerhalten, wenn Sie JSON erwarten , werden Sie höchstwahrscheinlich umgeleitet. In meinem Fall lade ich die Seite einfach neu und sie wird auf die Anmeldeseite umgeleitet. Oh, und überprüfen Sie, ob der jqXHR-Status 200 ist, was albern erscheint, weil Sie sich in der Fehlerfunktion befinden, oder? Andernfalls erzwingen legitime Fehlerfälle ein iteratives Neuladen (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
Vielen Dank Brian, Ihre Antwort war die beste für mein Szenario, obwohl ich mir wünschen würde, wenn es eine sicherere Prüfung gäbe, z. B. den Vergleich, zu welcher URL / Seite umgeleitet wird, anstelle der einfachen Prüfung des "Inhaltstyps". Ich konnte nicht finden, zu welcher Seite vom jqXHR-Objekt umgeleitet wird.
Johnny

Ich habe nach Status 401 gesucht und dann umgeleitet. Funktioniert wie ein Champion.
Eric

55

Verwenden Sie den Low-Level- $.ajax()Anruf:

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

Versuchen Sie dies für eine Weiterleitung:

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

Ich habe versucht, das Zeug auf niedrigem Niveau zu vermeiden. Angenommen, ich verwende so etwas wie das, was Sie beschreiben. Wie erzwinge ich eine Browserumleitung, wenn ich feststelle, dass der HTTP-Code 3xx ist? Mein Ziel ist es, den Benutzer umzuleiten und nicht nur bekannt zu geben, dass seine Sitzung abgelaufen ist.
Elliot Vargas

4
Übrigens ist $ .ajax () nicht sehr, sehr niedrig. In Bezug auf jQuery ist es nur ein niedriges Niveau, da es $ .get, $ .post usw. gibt, die viel einfacher sind als $ .ajax und alle seine Optionen.
Bis zum

16
Oh Junge! Entschuldigung, ich musste Ihre Antwort "nicht akzeptieren", sie ist immer noch sehr hilfreich. Die Umleitungen werden automatisch von XMLHttpRequest verwaltet, daher erhalte ich nach der Umleitung IMMER einen 200-Statuscode (seufz!). Ich denke, ich muss etwas Böses tun, wie den HTML-Code zu analysieren und nach einem Marker zu suchen.
Elliot Vargas

1
Nur neugierig, wenn die Sitzung auf dem Server endet, heißt das nicht, dass der Server eine andere SESSIONID sendet? konnten wir das nicht einfach erkennen?
Salamander2007

10
HINWEIS: Dies funktioniert nicht für Weiterleitungen. ajax wechselt zur neuen Seite und gibt den Statuscode zurück.

33

Ich wollte nur meinen Ansatz teilen, da dies jemandem helfen könnte:

Ich habe im Grunde ein JavaScript-Modul eingefügt, das die Authentifizierung wie die Anzeige des Benutzernamens und auch in diesem Fall die Weiterleitung zur Anmeldeseite behandelt .

Mein Szenario: Wir haben im Grunde einen ISA-Server dazwischen, der alle Anfragen abhört und mit einem 302 und einem Standort-Header auf unsere Anmeldeseite antwortet .

In meinem JavaScript-Modul war mein ursprünglicher Ansatz so etwas wie

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

Das Problem (wie viele hier bereits erwähnt haben) ist, dass der Browser die Umleitung selbst erledigt, weshalb mein ajaxCompleteRückruf nie aufgerufen wurde, sondern die Antwort der bereits umgeleiteten Anmeldeseite, die offensichtlich eine war status 200. Das Problem: Wie erkennen Sie, ob die erfolgreiche 200-Antwort Ihre tatsächliche Anmeldeseite oder nur eine andere beliebige Seite ist?

Die Lösung

Da ich 302 Weiterleitungsantworten nicht erfassen konnte, fügte ich LoginPagemeiner Anmeldeseite einen Header hinzu, der die URL der Anmeldeseite selbst enthielt. Im Modul höre ich jetzt auf den Header und mache eine Umleitung:

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

... und das funktioniert wie Charme :). Sie fragen sich vielleicht, warum ich die URL in den LoginPageHeader aufgenommen habe ... im Grunde genommen, weil ich keine Möglichkeit gefunden habe, die URL zu bestimmen, die GETsich aus der automatischen Standortumleitung vom xhrObjekt ergibt ...


+1 - aber benutzerdefinierte Header sollten damit beginnen X-, daher wäre ein besserer Header zu verwenden X-LoginPage: http://example.com/login.
Uınbɐɥs

6
@ShaquinTrifonoff Nicht mehr. Ich habe das X-Präfix nicht verwendet, da im Juni 2011 in einem ITEF-Dokument die Ablehnung vorgeschlagen wurde. Mit Juni 2012 ist es kein Beamter, dem benutzerdefinierte Header nicht mehr vorangestellt werden sollten X-.
Juri

Wir haben auch einen ISA-Server und ich bin gerade auf das gleiche Problem gestoßen. Anstatt es im Code zu umgehen , haben wir die Anweisungen in kb2596444 verwendet , um ISA so zu konfigurieren, dass die Umleitung beendet wird.
Scott Stone

29

Ich weiß, dass dieses Thema alt ist, aber ich werde noch einen anderen Ansatz nennen, den ich hier gefunden und zuvor beschrieben habe . Grundsätzlich verwende ich ASP.MVC mit WIF (dies ist jedoch für den Kontext dieses Themas nicht wirklich wichtig - die Antwort ist ausreichend, unabhängig davon, welche Frameworks verwendet werden. Der Hinweis bleibt unverändert - und behandelt Probleme im Zusammenhang mit Authentifizierungsfehlern beim Ausführen von Ajax-Anforderungen ) .

Der unten gezeigte Ansatz kann auf alle sofort einsatzbereiten Ajax-Anforderungen angewendet werden (wenn sie das Ereignis vor dem Senden offensichtlich nicht neu definieren).

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

Bevor eine Ajax-Anforderung ausgeführt wird, wird die CheckPulseMethode aufgerufen (die Controller-Methode, die am einfachsten sein kann):

[Authorize]
public virtual void CheckPulse() {}

Wenn der Benutzer nicht authentifiziert ist (Token abgelaufen ist), kann auf diese Methode nicht zugegriffen werden (geschützt durch AuthorizeAttribut). Da das Framework die Authentifizierung verwaltet, während das Token abläuft, setzt es den http-Status 302 auf die Antwort. Wenn Sie nicht möchten, dass Ihr Browser die 302-Antwort transparent verarbeitet, fangen Sie sie in Global.asax ab und ändern Sie den Antwortstatus - beispielsweise auf 200 OK. Fügen Sie außerdem einen Header hinzu, der Sie anweist, eine solche Antwort auf besondere Weise zu verarbeiten (später auf der Clientseite):

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

Überprüfen Sie schließlich auf der Clientseite, ob ein solcher benutzerdefinierter Header vorhanden ist. Wenn vorhanden, sollte eine vollständige Umleitung zur Anmeldeseite erfolgen (in meinem Fall window.locationwird die URL von der Anfrage ersetzt, die von meinem Framework automatisch verarbeitet wird).

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

Ich habe das Problem behoben, indem ich das PostAuthenticateRequest-Ereignis anstelle des EndRequest-Ereignisses verwendet habe.
Frinavale

@JaroslawWaliszko Ich habe das falsche Ereignis in meine letzte Antwort eingefügt! Ich meinte das PreSendRequestHeaders-Ereignis ..... nicht PostAuthenticateRequest! >> erröten << danke, dass du auf meinen Fehler hingewiesen hast.
Frinavale

@JaroslawWaliszko Wenn Sie WIF verwenden, können Sie auch 401 Antworten für AJAX-Anfragen zurückgeben und Ihr Javascript diese verarbeiten lassen. Außerdem gehen Sie davon aus, dass alle 302 eine Authentifizierung erfordern, die möglicherweise nicht in allen Fällen zutrifft. Ich habe eine Antwort hinzugefügt, wenn jemand interessiert ist.
Rob

26

Ich denke, ein besserer Weg, dies zu handhaben, besteht darin, die vorhandenen HTTP-Protokoll-Antwortcodes speziell zu nutzen 401 Unauthorized.

So habe ich es gelöst:

  1. Serverseite: Wenn die Sitzung abläuft und die Anforderung Ajax ist. Senden Sie einen 401-Antwortcode-Header
  2. Client-Seite: Binden Sie an die Ajax-Ereignisse

    $('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 ist dies allgemeiner und Sie schreiben keine neuen benutzerdefinierten Spezifikationen / Header. Sie sollten auch keinen Ihrer vorhandenen Ajax-Aufrufe ändern müssen.

Bearbeiten: Gemäß dem Kommentar von @ Rob unten sollte 401 (der HTTP-Statuscode für Authentifizierungsfehler) der Indikator sein. Weitere Informationen finden Sie unter 403 Verbotene vs 401 Nicht autorisierte HTTP-Antworten . Vor diesem Hintergrund verwenden einige Web-Frameworks 403 sowohl für Authentifizierungs- als auch für Autorisierungsfehler. Passen Sie sie daher entsprechend an. Danke Rob.


Ich benutze den gleichen Ansatz. Ruft jQuery ajaxSuccess bei 403-Fehlercode wirklich auf? Ich denke, dass nur ein AjaxError-Teil tatsächlich benötigt wird
Marius Balčytis

24

Ich habe dieses Problem folgendermaßen gelöst:

Fügen Sie eine Middleware hinzu, um die Antwort zu verarbeiten. Wenn es sich um eine Umleitung für eine Ajax-Anforderung handelt, ändern Sie die Antwort in eine normale Antwort mit der Umleitungs-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

Wenn die Antwort in ajaxComplete eine Umleitung enthält, muss es sich um eine Umleitung handeln. Ändern Sie daher den Speicherort des Browsers.

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

Ich habe eine einfache Lösung, die für mich funktioniert, keine Änderung des Servercodes erforderlich ... fügen Sie einfach einen Teelöffel Muskatnuss hinzu ...

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

Ich überprüfe das Vorhandensein eines HTML-Tags, aber Sie können den indexOf ändern, um nach einer eindeutigen Zeichenfolge auf Ihrer Anmeldeseite zu suchen ...


Dies scheint für mich nicht zu funktionieren, es ruft weiterhin die mit Ajax-Aufruf definierte Funktion auf, es ist, als würde es die Erfolgsmethode nicht überschreiben.
Adriaanp

Scheint zumindest nicht mehr für mich zu funktionieren. Mögliche Erklärung hier: stackoverflow.com/a/12010724/260665
Raj Pawan Gumdal

20

Eine andere Lösung, die ich gefunden habe (besonders nützlich, wenn Sie ein globales Verhalten festlegen möchten), besteht darin, die $.ajaxsetup()Methode zusammen mit der statusCodeEigenschaft zu verwenden . Verwenden Sie, wie bereits erwähnt, keinen Redirect-Statuscode ( 3xx), sondern einen 4xxStatuscode und behandeln Sie die Weiterleitung clientseitig.

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

Ersetzen Sie 400durch den Statuscode, den Sie verarbeiten möchten. Wie schon erwähnt 401 Unauthorizedkönnte eine gute Idee sein. Ich benutze das, 400da es sehr unspezifisch ist und ich das 401für spezifischere Fälle verwenden kann (wie falsche Anmeldeinformationen). Anstatt direkt umzuleiten, sollte Ihr Backend einen 4xxFehlercode zurückgeben, wenn die Sitzung abgelaufen ist und Sie die Umleitung clientseitig durchführen. Funktioniert perfekt für mich, auch mit Frameworks wie backbone.js


Wo soll die Funktion auf Seite erwähnt werden?
Vikrant

@Vikrant Wenn ich Ihre Frage richtig verstehe, können Sie die Funktion direkt nach dem Laden von jQuery und bevor Sie Ihre eigentlichen Anforderungen ausführen, aufrufen.
morten.c

19

Die meisten der angegebenen Lösungen verwenden eine Problemumgehung, indem sie einen zusätzlichen Header oder einen ungeeigneten HTTP-Code verwenden. Diese Lösungen werden höchstwahrscheinlich funktionieren, fühlen sich aber ein bisschen "hacky" an. Ich habe eine andere Lösung gefunden.

Wir verwenden WIF, das für die Umleitung (passiveRedirectEnabled = "true") für eine 401-Antwort konfiguriert ist. Die Umleitung ist nützlich, wenn normale Anforderungen verarbeitet werden, funktioniert jedoch nicht für AJAX-Anforderungen (da Browser die 302 / -Umleitung nicht ausführen).

Mit dem folgenden Code in Ihrer Datei global.asax können Sie die Umleitung für AJAX-Anforderungen deaktivieren:

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

Auf diese Weise können Sie 401 Antworten für AJAX-Anforderungen zurückgeben, die Ihr Javascript dann durch erneutes Laden der Seite verarbeiten kann. Beim erneuten Laden der Seite wird ein 401 ausgegeben, der von WIF verarbeitet wird (und WIF leitet den Benutzer zur Anmeldeseite weiter).

Ein Beispiel für Javascript zur Behandlung von 401-Fehlern:

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

Gute Lösung. Vielen Dank.
DanMan

19

Dieses Problem kann dann bei Verwendung der ASP.NET MVC RedirectToAction-Methode auftreten. Um zu verhindern, dass das Formular die Antwort in div anzeigt, können Sie einfach eine Art Ajax-Antwortfilter für eingehende Antworten mit $ .ajaxSetup ausführen . Wenn die Antwort eine MVC-Umleitung enthält, können Sie diesen Ausdruck auf der JS-Seite auswerten. Beispielcode für JS unten:

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

Wenn die Daten lauten : "window.location = '/ Acount / Login'" über dem Filter, wird dies abgefangen und ausgewertet, um die Umleitung vorzunehmen, anstatt die Daten anzeigen zu lassen.


databefindet sich im Antworttext oder in der Kopfzeile?
GMsoF

16

Zusammenstellen, was Vladimir Prudnikov und Thomas Hansen gesagt haben:

  • Ändern Sie Ihren serverseitigen Code, um festzustellen, ob es sich um eine XHR handelt. Wenn dies der Fall ist, setzen Sie den Antwortcode der Umleitung auf 278. In django:
   if request.is_ajax():
      response.status_code = 278

Dadurch behandelt der Browser die Antwort als Erfolg und gibt sie an Ihr Javascript weiter.

  • Stellen Sie in Ihrem JS sicher, dass die Formularübermittlung über Ajax erfolgt, überprüfen Sie den Antwortcode und leiten Sie ihn bei Bedarf um:
$('#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

Versuchen

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

Stellen Sie es auf die Anmeldeseite. Wenn es in ein div auf der Hauptseite geladen wurde, wird es auf die Anmeldeseite umgeleitet. "#site" ist eine ID eines div, die sich auf allen Seiten außer der Anmeldeseite befindet.


12

Während die Antworten für Benutzer zu funktionieren scheinen, wenn Sie Spring Security verwenden, habe ich festgestellt, dass LoginUrlAuthenticationEntryPoint erweitert und spezifischer Code hinzugefügt wird, um AJAX robuster zu handhaben. Die meisten Beispiele fangen alle Weiterleitungen ab, nicht nur Authentifizierungsfehler. Dies war für das Projekt, an dem ich arbeite, unerwünscht. Möglicherweise müssen Sie auch ExceptionTranslationFilter erweitern und die Methode "sendStartAuthentication" überschreiben, um den Caching-Schritt zu entfernen, wenn die fehlgeschlagene AJAX-Anforderung nicht zwischengespeichert werden soll.

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

Quellen: 1 , 2


3
Es wäre (für mich) hilfreich, wenn die Wähler erklären würden, warum sie nicht wählen. Wenn diese Lösung etwas Schlechtes enthält, möchte ich aus meinen Fehlern lernen. Vielen Dank.
John

Wenn Sie Spring und auch JSF verwenden, überprüfen Sie Folgendes: ("partiell / ajax"). EqualsIgnoreCase (request.getHeader ("Face-Request"));
J Slick

Benutzer haben möglicherweise abgestimmt, weil Sie nicht erwähnt haben: (1) die erforderlichen clientseitigen Mods, um Ihre Fehlerantwort zu erkennen; (2) die erforderlichen Modifikationen für die Spring-Konfiguration, um Ihren angepassten LoginUrlAuthenticationEntryPoint zur Filterkette hinzuzufügen.
J Slick

Ihre Antwort ähnelt der von @Arpad. Es hat bei mir funktioniert; Verwenden von Spring Security 3.2.9. stackoverflow.com/a/8426947/4505142
Darren Parker

11

Ich habe dieses Problem gelöst, indem ich Folgendes in meine login.php-Seite eingefügt habe.

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

10

Lassen Sie mich noch einmal das von @Steg beschriebene Problem zitieren

Ich hatte ein ähnliches Problem wie Sie. Ich führe eine Ajax-Anfrage mit zwei möglichen Antworten durch: eine, die den Browser auf eine neue Seite umleitet, und eine, die ein vorhandenes HTML-Formular auf der aktuellen Seite durch ein neues ersetzt.

IMHO ist dies eine echte Herausforderung und muss offiziell auf die aktuellen HTTP-Standards erweitert werden.

Ich glaube, der neue HTTP-Standard wird darin bestehen, einen neuen Statuscode zu verwenden. Bedeutung: 301/302Weist den Browser derzeit an, den Inhalt dieser Anforderung in eine neue zu holen location.

Im erweiterten Standard heißt es, dass status: 308der Browser bei einer Antwort (nur ein Beispiel) die Hauptseite auf die locationbereitgestellte Seite umleiten sollte .

Davon abgesehen; Ich bin geneigt, dieses zukünftige Verhalten bereits nachzuahmen. Wenn daher eine document.redirect benötigt wird, muss der Server wie folgt antworten:

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

Wenn JS das " status: 204" erhält , prüft es, ob der x-status: 308Header vorhanden ist, und führt eine document.redirect zu der im locationHeader angegebenen Seite durch.

Macht das für Sie Sinn?


7

Einige mögen das Folgende nützlich finden:

Ich wollte, dass Clients für jede Restaktion, die ohne Autorisierungstoken gesendet wird, auf die Anmeldeseite umgeleitet werden. Da alle meine Restaktionen auf Ajax basieren, brauchte ich eine gute generische Methode, um zur Anmeldeseite umzuleiten, anstatt die Ajax-Erfolgsfunktion zu verwenden.

Folgendes habe ich getan:

Bei jeder Ajax-Anfrage gibt mein Server eine Json 200-Antwort "NEED TO AUTHENTICATE" zurück (wenn sich der Client authentifizieren muss).

Einfaches Beispiel in Java (Serverseite):

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

In meinem Javascript habe ich folgenden Code hinzugefügt:

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

Und das war's auch schon.


5

Im Servlet sollten response.setStatus(response.SC_MOVED_PERMANENTLY); Sie den Status '301' xmlHttp senden, den Sie für eine Umleitung benötigen ...

und in der $ .ajax-Funktion sollten Sie die .toString()Funktion nicht verwenden ..., nur

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

Das Problem ist, dass es nicht sehr flexibel ist. Sie können sich nicht entscheiden, wohin Sie umleiten möchten.

Die Umleitung durch die Servlets sollte der beste Weg sein. aber ich kann immer noch nicht den richtigen Weg finden, es zu tun.


5

Ich wollte mich nur an alle Ajax-Anfragen für die gesamte Seite halten. @ SuperG hat mich dazu gebracht. Folgendes habe ich erreicht:

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

Ich wollte speziell nach bestimmten http-Statuscodes suchen, um meine Entscheidung zu treffen. Sie können sich jedoch einfach an ajaxError binden, um etwas anderes als Erfolg zu erzielen (nur 200 vielleicht?). Ich hätte gerade schreiben können:

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

1
Letzteres würde alle anderen Fehler verbergen, die die Fehlerbehebung problematisch machen
Tim Abell

1
Ein 403 bedeutet nicht, dass der Benutzer nicht authentifiziert ist, sondern dass der (wahrscheinlich authentifizierte) Benutzer keine Berechtigung zum Anzeigen der angeforderten Ressource hat. Es sollte also nicht zur Anmeldeseite umgeleitet werden
Rob

5

Wenn Sie auch die Werte übergeben möchten, können Sie auch die Sitzungsvariablen festlegen und auf Eg zugreifen: In Ihrem JSP können Sie schreiben

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

Und dann können Sie diesen temporären Wert in Ihrer Javascript-Variablen speichern und herumspielen


5

Ich hatte keinen Erfolg mit der Header-Lösung - sie wurden in meiner ajaxSuccess / ajaxComplete-Methode nie erfasst. Ich habe Stegs Antwort mit der benutzerdefinierten Antwort verwendet, aber die JS-Seite etwas geändert. Ich richte eine Methode ein, die ich in jeder Funktion aufrufe, damit ich Standard $.getund $.postMethoden verwenden kann.

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

Beispiel dafür in Gebrauch ...

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

Schließlich löse ich das Problem durch Hinzufügen eines benutzerdefinierten HTTP Header. Kurz vor der Antwort für jede Anfrage auf der Serverseite füge ich die aktuell angeforderte URL zum Header der Antwort hinzu.

Mein Anwendungstyp auf dem Server ist Asp.Net MVC, und es hat einen guten Ort, um dies zu tun. in Global.asaxich implementierte die Application_EndRequestVeranstaltung so:

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

    }

Es funktioniert perfekt für mich! Jetzt in jeder Antwort von der JQuery $.postich habe die angeforderten urlund auch anderen Response - Header , die als Ergebnis kommt POSTdurch Statusverfahren 302, 303....

und eine andere wichtige Sache ist, dass es nicht notwendig ist, Code auf der Server- oder Client-Seite zu ändern.

und die nächste ist die Möglichkeit, auf diese Weise auf die anderen Informationen der Nachaktion wie Fehler, Nachrichten und ... zuzugreifen.

Ich habe das gepostet, vielleicht jemandem helfen :)


4

Ich hatte dieses Problem in einer Django-App, an der ich bastele (Haftungsausschluss: Ich bastele am Lernen und bin in keiner Weise ein Experte). Ich wollte mit jQuery ajax eine DELETE-Anforderung an eine Ressource senden, sie auf der Serverseite löschen und dann eine Umleitung zurück zur (im Grunde) Homepage senden. Als ich HttpResponseRedirect('/the-redirect/')vom Python-Skript aus sendete , empfing die Ajax-Methode von jQuery 200 anstelle von 302. Ich habe also eine Antwort von 300 gesendet mit:

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

Dann habe ich die Anfrage auf dem Client mit jQuery.ajax wie folgt gesendet / bearbeitet:

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

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

Vielleicht ist die Verwendung von 300 nicht "richtig", aber zumindest hat es so funktioniert, wie ich es wollte.

PS: Das war ein großer Aufwand für die mobile Version von SO. Dummer ISP hat meine Servicestornierungsanfrage richtig gestellt, als ich mit meiner Antwort fertig war!


4

Sie können auch den XMLHttpRequest-Sendeprototyp einbinden. Dies funktioniert für alle Sends (jQuery / dojo / etc) mit einem Handler.

Ich habe diesen Code geschrieben, um einen abgelaufenen Fehler von 500 Seiten zu behandeln, aber er sollte genauso gut funktionieren, um eine 200-Umleitung abzufangen. Bereiten Sie den Wikipedia-Eintrag auf XMLHttpRequest onreadystatechange über die Bedeutung von readyState vor.

// 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

Außerdem möchten Sie den Benutzer wahrscheinlich auf die in den Headern angegebene URL umleiten. Endlich sieht es so aus:

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

UPD: Opps. Habe die gleiche Aufgabe, aber es funktioniert nicht. Mach das Zeug. Ich zeige Ihnen die Lösung, wenn ich sie finde.


4
Diese Antwort sollte vom Autor gelöscht werden, da er selbst sagt, dass dies nicht funktioniert. Er kann später eine funktionierende Lösung veröffentlichen. -1
TheCrazyProgrammer

Die Idee ist gut, aber das Problem ist, dass der Header "Location" niemals übergeben wird.
Martin Zvarík

Meine Bearbeitung wurde abgelehnt, also ... müssen Sie "var xmlHttp = $ .ajax ({...." verwenden, die Variable darf nicht enthalten sein ... dann verwenden Sie: console.log (xmlHttp.getAllResponseHeaders ()) ;
Martin Zvarík

2

Ich habe eine funktionierende Lösung mit den Antworten von @John und @Arpad Link und @RobWinch Link erhalten

Ich verwende Spring Security 3.2.9 und jQuery 1.10.2.

Erweitern Sie die Spring-Klasse, um eine 4XX-Antwort nur von AJAX-Anforderungen zu verursachen:

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>

Fügen Sie in meinen JSPs einen globalen AJAX-Fehlerbehandler hinzu, wie hier gezeigt

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

Entfernen Sie außerdem vorhandene Fehlerbehandlungsroutinen aus AJAX-Aufrufen auf JSP-Seiten:

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

Ich hoffe es hilft anderen.

Update1 Ich habe festgestellt, dass ich die Option (always-use-default-target = "true") zur Formularanmeldekonfiguration hinzufügen muss. Dies war erforderlich, da Spring, nachdem eine AJAX-Anforderung (aufgrund einer abgelaufenen Sitzung) auf die Anmeldeseite umgeleitet wurde, die vorherige AJAX-Anforderung merkt und nach der Anmeldung automatisch zu dieser umleitet. Dadurch wird der zurückgegebene JSON auf der Browserseite angezeigt. Natürlich nicht was ich will.

Update2 Verwenden Sie anstelle von always-use-default-target="true"@RobWinch das Beispiel zum Blockieren von AJAX-Anforderungen aus dem requstCache. Dadurch können normale Links nach der Anmeldung zu ihrem ursprünglichen Ziel umgeleitet werden, AJAX wechselt jedoch nach der Anmeldung zur Startseite.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.