Come gestire una richiesta di reindirizzamento dopo una chiamata Ajax jQuery


1370

Sto usando $.post()per chiamare un servlet usando Ajax e quindi usando il frammento HTML risultante per sostituire un divelemento nella pagina corrente dell'utente. Tuttavia, se la sessione scade, il server invia una direttiva di reindirizzamento per inviare l'utente alla pagina di accesso. In questo caso, jQuery sta sostituendo l' divelemento con il contenuto della pagina di accesso, costringendo gli occhi dell'utente a testimoniare davvero una scena rara.

Come posso gestire una direttiva di reindirizzamento da una chiamata Ajax con jQuery 1.2.6?


1
(non una risposta in quanto tale) - L'ho fatto in passato modificando la libreria jquery e aggiungendo un controllo per la pagina di accesso su ogni XHR completo. Non è la soluzione migliore perché dovrebbe essere eseguita ogni volta che si aggiorna, ma risolve il problema.
Sugendran,

1
Vedere domanda relativa: stackoverflow.com/questions/5941933/...
Nutel

Il HttpContext.Response.AddHeadere check at ajaxsetup success è la strada da percorrere
LCJ,

4
Perché il server non può restituire 401? In tal caso puoi avere un $ .ajaxSetup globale e usare il codice di stato per reindirizzare la pagina.
Vishal,

1
questo link doanduyhai.wordpress.com/2012/04/21/… mi dà la soluzione giusta
pappu_kutty

Risposte:


697

Ho letto questa domanda e ho implementato l'approccio che è stato affermato riguardo all'impostazione del codice di stato HTTP di risposta su 278 per evitare che il browser gestisca in modo trasparente i reindirizzamenti. Anche se ha funzionato, ero un po 'insoddisfatto perché è un po' un trucco.

Dopo aver scavato di più, ho abbandonato questo approccio e ho usato JSON . In questo caso, tutte le risposte alle richieste AJAX hanno il codice di stato 200 e il corpo della risposta contiene un oggetto JSON che è costruito sul server. JavaScript sul client può quindi utilizzare l'oggetto JSON per decidere cosa deve fare.

Ho avuto un problema simile al tuo. Eseguo una richiesta AJAX che ha 2 possibili risposte: una che reindirizza il browser a una nuova pagina e una che sostituisce un modulo HTML esistente nella pagina corrente con uno nuovo. Il codice jQuery per fare questo assomiglia a qualcosa del tipo:

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

L'oggetto "dati" JSON è costruito sul server per avere 2 membri: data.redirecte data.form. Ho trovato questo approccio molto migliore.


58
Come indicato nella soluzione in stackoverflow.com/questions/503093/... è meglio usare window.location.replace (data.redirect); di window.location.href = data.redirect;
Carles Barrobés,

8
Qualsiasi motivo per cui non sarebbe meglio usare i codici HTTP sull'azione. Ad esempio un codice 307 che è il reindirizzamento temporaneo HTTP?
Sergei Golos,

15
@Sergei Golos il motivo è che se si esegue un reindirizzamento HTTP, il reindirizzamento in realtà non arriva mai al callback di successo ajax. Il browser elabora il reindirizzamento consegnando un codice 200 con il contenuto della destinazione del reindirizzamento.
Miguel Silva,

2
come sarebbe il codice del server? per avere un valore di ritorno di data.form e data.redirect. fondamentalmente come posso determinare se inserisco un reindirizzamento in esso?
Vnge,

6
Questa risposta sarebbe più utile se avesse mostrato come si fa sul server.
Pedro Hoehl Carvalho,

243

Ho risolto questo problema con:

  1. Aggiunta di un'intestazione personalizzata alla risposta:

    public ActionResult Index(){
        if (!HttpContext.User.Identity.IsAuthenticated)
        {
            HttpContext.Response.AddHeader("REQUIRES_AUTH","1");
        }
        return View();
    }
  2. Associare una funzione JavaScript ajaxSuccessall'evento e verificare se esiste l'intestazione:

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

6
Che soluzione fantastica. Mi piace l'idea di una soluzione completa. Devo verificare lo stato 403, ma posso usare il bind ajaxSuccess sul corpo per questo (che è quello che stavo davvero cercando.) Grazie.
Brettico

4
Ho appena fatto questo e ho scoperto che avevo bisogno di ajaxComplete dove stavo usando la funzione $ .get () e qualsiasi stato diverso da 200 non era attivo. In effetti, avrei potuto probabilmente limitarmi a AjaxError invece. Vedi la mia risposta di seguito per maggiori dettagli.
Brettico

2
Va bene in molti casi, ma cosa succede se il tuo framework gestisce le autorizzazioni?
jwaliszko,

13
Mi piace l'approccio dell'intestazione, ma penso anche - come @ mwoods79 - che la conoscenza di dove reindirizzare non dovrebbe essere duplicata. L'ho risolto aggiungendo un'intestazione REDIRECT_LOCATION invece di un valore booleano.
Rintcius,

3
Fare attenzione a impostare l'intestazione sulla risposta DOPO il reindirizzamento. Come descritto in altre risposte in questa pagina, il reindirizzamento potrebbe essere trasparente per il gestore ajaxSucces. Quindi ho incluso l'intestazione nella risposta GET della pagina di accesso (che alla fine stava solo innescando ajaxSuccess nel mio scenario).
sieppl,

118

Nessun browser gestisce correttamente le risposte 301 e 302. E in effetti lo standard dice anche che dovrebbero gestirli "in modo trasparente", il che è un MASSIVO mal di testa per i fornitori di Ajax Library. In Ra-Ajax siamo stati costretti a utilizzare il codice di stato della risposta HTTP 278 (solo un codice di successo "non utilizzato") per gestire i reindirizzamenti trasparenti dal server ...

Questo mi dà davvero fastidio, e se qualcuno qui ha qualche "pull" nel W3C apprezzerei che tu possa far sapere al W3C che dobbiamo davvero gestire noi stessi i codici 301 e 302 ...! ;)


3
Per prima cosa, 278 dovrebbe diventare parte delle specifiche HTTP ufficiali.
Chris Marisic,

2
Non li gestisce già in modo trasparente? Se una risorsa è stata spostata, gestirla in modo trasparente significa ripetere la richiesta sull'URL fornito. Questo è quello che mi aspetterei utilizzando l'API XMLHttpRequest.
Philippe Rathé,

@ PhilippeRathé Accetto. La gestione trasparente è proprio quello che voglio. E non so perché sia ​​considerato cattivo.
smwikipedia,

@smwikipedia Per organizzare un reindirizzamento del markup nella sezione principale senza reindirizzare la pagina.
cmc

se qualcuno qui ha qualche "pull" nel W3C apprezzerei che tu possa far sapere al W3C che dobbiamo davvero gestire noi stessi i codici 301 e 302 ...! ;)
Tommy.Tang

100

La soluzione che è stata infine implementata è stata quella di utilizzare un wrapper per la funzione di callback della chiamata Ajax e in questo wrapper verificare l'esistenza di un elemento specifico sul blocco HTML restituito. Se l'elemento è stato trovato, il wrapper ha eseguito un reindirizzamento. In caso contrario, il wrapper ha inoltrato la chiamata alla funzione di richiamata effettiva.

Ad esempio, la nostra funzione wrapper era qualcosa del tipo:

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

Quindi, quando abbiamo fatto la chiamata Ajax abbiamo usato qualcosa come:

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

Questo ha funzionato per noi perché tutte le chiamate Ajax hanno sempre restituito HTML all'interno di un elemento DIV che usiamo per sostituire un pezzo della pagina. Inoltre, dovevamo solo reindirizzare alla pagina di accesso.


4
Si noti che questo può essere abbreviato in funzione cbWrapper (funct) {funzione return (data) {if ($ ("# myForm", data) .size ()> 0) top.location.href = "login"; else funct (dati); }}. Quindi è necessario solo cbWrapper (myActualCB) quando si chiama .post. Sì, il codice nei commenti è un disastro ma va notato :)
Simen Echholt

la dimensione è ammortizzata in modo da poter usare .length qui al posto della dimensione
sunil

66

Mi piace il metodo di Timmerz con un leggero tocco di limone. Se ti viene mai restituito contentType di text / html quando ti aspetti JSON , molto probabilmente verrai reindirizzato. Nel mio caso, ho semplicemente ricaricato la pagina e viene reindirizzata alla pagina di accesso. Oh, e controlla che lo stato jqXHR sia 200, il che sembra sciocco, perché sei nella funzione di errore, giusto? In caso contrario, i casi di errore legittimi imporranno un ricaricamento iterativo (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
grazie mille Brian, la tua risposta è stata la migliore per il mio scenario, anche se mi piacerebbe che ci fosse un controllo più sicuro come il confronto a quale url / pagina sta reindirizzando al posto del semplice controllo "content-type". Non sono riuscito a trovare a quale pagina viene reindirizzato dall'oggetto jqXHR.
Johnny,

Ho verificato lo stato 401 e quindi il reindirizzamento. Funziona come un campione.
Eric,

55

Usa la $.ajax()chiamata di basso livello :

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

Prova questo per un reindirizzamento:

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

Stavo cercando di evitare le cose di basso livello. Ad ogni modo, supponiamo che io usi qualcosa di simile a quello che descrivi, come posso forzare un reindirizzamento del browser quando rilevo che il codice HTTP è 3xx? Il mio obiettivo è reindirizzare l'utente, non solo per annunciare che la sua sessione è scaduta.
Elliot Vargas,

4
A proposito, $ .ajax () non è un livello molto, molto basso. È solo di basso livello in termini di jQuery perché ci sono $ .get, $ .post, ecc. Che sono molto più semplici di $ .ajax e di tutte le sue opzioni.
Fino al

16
Oh ragazzo! Scusa se ho dovuto "non accettare" la tua risposta, è comunque molto utile. Il fatto è che i reindirizzamenti sono gestiti automaticamente da XMLHttpRequest, quindi ottengo SEMPRE un codice di stato 200 dopo il reindirizzamento (sospiro!). Penso che dovrò fare qualcosa di brutto come analizzare l'HTML e cercare un marker.
Elliot Vargas,

1
Solo curioso, se la sessione termina sul server, ciò non significa che il server invia un SESSIONID diverso? non potremmo semplicemente rilevarlo?
Salamander2007,

10
NOTA: questo non funziona per i reindirizzamenti. ajax andrà alla nuova pagina e restituirà il suo codice di stato.

33

Volevo solo condividere il mio approccio in quanto ciò potrebbe aiutare qualcuno:

Fondamentalmente ho incluso un modulo JavaScript che gestisce le cose di autenticazione come la visualizzazione del nome utente e anche in questo caso la gestione del reindirizzamento alla pagina di accesso .

Il mio scenario: Fondamentalmente abbiamo un server ISA tra il quale ascolta tutte le richieste e risponde con un 302 e un'intestazione di posizione alla nostra pagina di accesso.

Nel mio modulo JavaScript il mio approccio iniziale era qualcosa di simile

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

Il problema (come molti qui già menzionati) è che il browser gestisce il reindirizzamento da solo, pertanto il mio ajaxCompletecallback non è mai stato chiamato, ma invece ho avuto la risposta della pagina di accesso già reindirizzata che ovviamente erastatus 200 . Il problema: come si rileva se la risposta 200 corretta è la pagina di accesso effettiva o solo qualche altra pagina arbitraria ??

La soluzione

Poiché non sono stato in grado di acquisire 302 risposte di reindirizzamento, ho aggiunto LoginPageun'intestazione sulla mia pagina di accesso che conteneva l'URL della pagina di accesso stessa. Nel modulo ora ascolto l'intestazione e faccio un reindirizzamento:

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

... e funziona come un incanto :). Potresti chiederti perché includo l'URL LoginPagenell'intestazione ... beh fondamentalmente perché non ho trovato alcun modo per determinare l'url del GETrisultato del reindirizzamento automatico della posizione xhrdall'oggetto ...


+1 - ma le intestazioni personalizzate dovrebbero iniziare con X-, quindi un'intestazione migliore da usare sarebbe X-LoginPage: http://example.com/login.
uınbɐɥs

6
@ShaquinTrifonoff Non più. Non ho usato il prefisso X perché nel giugno 2011 un documento ITEF ha proposto la loro deprecazione e in effetti, con giugno 2012 non è ufficiale che le intestazioni personalizzate non debbano più avere il prefisso X-.
Juri,

Abbiamo anche un server ISA e ho riscontrato lo stesso problema. Invece di aggirare il problema nel codice, abbiamo usato le istruzioni in kb2596444 per configurare ISA per interrompere il reindirizzamento.
scott stone

29

So che questo argomento è vecchio, ma darò ancora un altro approccio che ho trovato e precedentemente descritto qui . Fondamentalmente sto usando ASP.MVC con WIF (ma questo non è davvero importante per il contesto di questo argomento - la risposta è adeguata indipendentemente dai framework utilizzati. L'indizio rimane invariato - gestendo i problemi relativi agli errori di autenticazione durante l'esecuzione di richieste Ajax ) .

L'approccio mostrato di seguito può essere applicato a tutte le richieste Ajax out of the box (se ovviamente non ridefiniscono prima di inviare l'evento ovviamente).

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

Prima di eseguire qualsiasi richiesta Ajax CheckPulseviene invocato il metodo (il metodo del controller che può essere qualcosa di più semplice):

[Authorize]
public virtual void CheckPulse() {}

Se l'utente non è autenticato (token scaduto) non è possibile accedere a tale metodo (protetto dall'attributo Authorize). Poiché il framework gestisce l'autenticazione, mentre il token scade, inserisce lo stato http 302 nella risposta. Se non desideri che il tuo browser gestisca la risposta 302 in modo trasparente, intercettala in Global.asax e modifica lo stato della risposta, ad esempio su 200 OK. Inoltre, aggiungi l'intestazione, che ti dice di elaborare tale risposta in modo speciale (in seguito sul lato client):

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

Infine, sul lato client, controlla tale intestazione personalizzata. Se presente, il reindirizzamento completo alla pagina di accesso dovrebbe essere eseguito (nel mio caso window.locationviene sostituito dall'URL della richiesta che viene gestito automaticamente dal mio 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;
                }
            }
    });
}

Ho risolto il problema utilizzando l'evento PostAuthenticateRequest anziché l'evento EndRequest.
Frinavale,

@JaroslawWaliszko Ho incollato l'evento sbagliato nella mia ultima risposta! Intendevo l'evento PreSendRequestHeaders ..... non PostAuthenticateRequest! >> arrossire << grazie per aver segnalato il mio errore.
Frinavale,

@JaroslawWaliszko quando usi WIF puoi anche restituire 401 risposte per richieste AJAX e lasciare che il tuo javascript le gestisca. Inoltre stai assumendo che tutti i 302 richiedano un'autenticazione che potrebbe non essere vera in tutti i casi. Ho aggiunto una risposta se qualcuno è interessato.
Rob,

26

Penso che un modo migliore per gestirlo sia sfruttare i codici di risposta del protocollo HTTP esistenti, in particolare 401 Unauthorized.

Ecco come l'ho risolto:

  1. Lato server: se la sessione scade e la richiesta è ajax. invia un'intestazione del codice di risposta 401
  2. Lato client: vincola agli eventi 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';
    }
    });

Questo è più generico e non stai scrivendo alcune nuove specifiche / intestazioni personalizzate. Inoltre, non è necessario modificare nessuna delle chiamate ajax esistenti.

Modifica: il commento di Per @ Rob in basso, 401 (il codice di stato HTTP per errori di autenticazione) dovrebbe essere l'indicatore. Vedi 403 Forbidden vs 401 risposte HTTP non autorizzate per maggiori dettagli. Detto questo, alcuni framework web usano 403 sia per gli errori di autenticazione che per quelli di autorizzazione, quindi adattati di conseguenza. Grazie Rob.


Uso lo stesso approccio. JQuery chiama davvero ajaxSuccess sul codice di errore 403? Penso che solo la parte ajaxError sia effettivamente necessaria
Marius Balčytis,

24

Ho risolto questo problema in questo modo:

Aggiungi un middleware per elaborare la risposta, se si tratta di un reindirizzamento per una richiesta Ajax, cambia la risposta in una risposta normale con l'URL di reindirizzamento.

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

Quindi in ajaxComplete, se la risposta contiene reindirizzamento, deve essere un reindirizzamento, quindi cambia la posizione del browser.

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

Ho una soluzione semplice che funziona per me, non è necessario modificare il codice del server ... basta aggiungere un cucchiaino di noce moscata ...

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

Controllo la presenza del tag html, ma è possibile modificare l'indiceOf per cercare qualsiasi stringa univoca presente nella pagina di accesso ...


Questo non sembra funzionare per me, continua a chiamare la funzione definita con una chiamata Ajax, è come se non prevalesse il metodo di successo.
adriaanp,

Non sembra funzionare per me, almeno più. Possibile spiegazione qui: stackoverflow.com/a/12010724/260665
Raj Pawan Gumdal

20

Un'altra soluzione che ho trovato (particolarmente utile se si desidera impostare un comportamento globale) è quella di utilizzare il $.ajaxsetup()metodo insieme alla statusCodeproprietà . Come altri hanno sottolineato, non utilizzare uno statuscode di reindirizzamento ( 3xx), utilizzare invece uno 4xxstatuscode e gestire il reindirizzamento lato client.

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

Sostituisci 400con lo statuscode che vuoi gestire. Come già accennato 401 Unauthorizedpotrebbe essere una buona idea. Io uso il 400poiché è molto aspecifico e posso usare il 401per casi più specifici (come credenziali di accesso errate). Pertanto, invece di reindirizzare direttamente il back-end dovrebbe restituire un 4xxcodice di errore al termine della sessione e gestire il reindirizzamento sul lato client. Funziona perfettamente per me anche con framework come backbone.js


Dove menzionare la funzione sulla pagina?
Vikrant,

@Vikrant Se capisco correttamente la tua domanda, puoi chiamare la funzione subito dopo che jQuery è stato caricato e prima di fare le tue effettive richieste.
morten.c

19

La maggior parte delle soluzioni fornite utilizza una soluzione alternativa, utilizzando un'intestazione aggiuntiva o un codice HTTP inadeguato. Quelle soluzioni probabilmente funzioneranno ma si sentiranno un po '"confuse". Ho trovato un'altra soluzione.

Stiamo usando WIF che è configurato per reindirizzare (passiveRedirectEnabled = "true") su una risposta 401. Il reindirizzamento è utile quando si gestiscono le richieste normali ma non funzionerà per le richieste AJAX (poiché i browser non eseguiranno il 302 / reindirizzamento).

Utilizzando il seguente codice in global.asax è possibile disabilitare il reindirizzamento per le richieste 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;
        }
    }

Ciò ti consente di restituire 401 risposte per richieste AJAX, che il tuo javascript può quindi gestire ricaricando la pagina. Il ricaricamento della pagina genererà un 401 che verrà gestito da WIF (e WIF reindirizzerà l'utente alla pagina di accesso).

Un esempio di javascript per gestire gli errori 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);
    }
});

Buona soluzione Grazie.
DanMan,

19

Questo problema può apparire quindi utilizzando il metodo ASP.NET MVC RedirectToAction. Per evitare che il modulo visualizzi la risposta in div, puoi semplicemente fare una sorta di filtro di risposta ajax per ricevere risposte con $ .ajaxSetup . Se la risposta contiene reindirizzamento MVC, è possibile valutare questa espressione sul lato JS. Codice di esempio per JS di seguito:

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

Se i dati sono: "window.location = '/ Acount / Login'" sopra il filtro lo catturerà e valuterà per effettuare il reindirizzamento invece di consentire la visualizzazione dei dati.


dataè nel corpo della risposta o nell'intestazione?
GMsoF,

16

Mettendo insieme ciò che Vladimir Prudnikov e Thomas Hansen hanno detto:

  • Modifica il codice sul lato server per rilevare se si tratta di un XHR. In tal caso, impostare il codice di risposta del reindirizzamento su 278. In django:
   if request.is_ajax():
      response.status_code = 278

In questo modo il browser considera la risposta come un successo e la passa al tuo Javascript.

  • Nel tuo JS, assicurati che l'invio del modulo sia tramite Ajax, controlla il codice di risposta e reindirizza se necessario:
$('#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

Provare

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

Mettilo nella pagina di accesso. Se è stato caricato in un div sulla pagina principale, reindirizzerà fino alla pagina di accesso. "#site" è un id di un div che si trova su tutte le pagine tranne la pagina di accesso.


12

Mentre le risposte sembrano funzionare per le persone se usi Spring Security, ho trovato l'estensione di LoginUrlAuthenticationEntryPoint e l'aggiunta di codice specifico per gestire AJAX in modo più affidabile. La maggior parte degli esempi intercetta tutti i reindirizzamenti, non solo gli errori di autenticazione. Questo non era auspicabile per il progetto su cui lavoro. Potrebbe essere necessario estendere anche ExceptionTranslationFilter e sovrascrivere il metodo "sendStartAuthentication" per rimuovere il passaggio di memorizzazione nella cache se non si desidera memorizzare nella cache la richiesta AJAX non riuscita.

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

Fonti: 1 , 2


3
Sarebbe utile (per me) se gli elettori in giù spiegassero perché votano in basso. Se c'è qualcosa di brutto in questa soluzione, vorrei imparare dai miei errori. Grazie.
Giovanni

Se usi Spring e usi anche JSF, controlla anche questo: ("partial / ajax"). EqualsIgnoreCase (request.getHeader ("faces-request"));
J Slick,

Gli utenti potrebbero aver votato verso il basso perché non hai menzionato: (1) le mod sul lato client necessarie per rilevare la tua risposta all'errore; (2) le mod necessarie alla configurazione di Spring per aggiungere il tuo LoginUrlAuthenticationEntryPoint personalizzato alla catena di filtri.
J Slick,

La tua risposta è simile a questa da @Arpad. Ha funzionato per me; utilizzando Spring Security 3.2.9. stackoverflow.com/a/8426947/4505142
Darren Parker

11

Ho risolto questo inserendo quanto segue nella mia pagina login.php.

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

10

Vorrei solo citare di nuovo il problema come descritto da @Steg

Ho avuto un problema simile al tuo. Eseguo una richiesta Ajax che ha 2 possibili risposte: una che reindirizza il browser a una nuova pagina e una che sostituisce un modulo HTML esistente nella pagina corrente con uno nuovo.

IMHO questa è una vera sfida e dovrà essere ufficialmente estesa agli attuali standard HTTP.

Credo che il nuovo standard Http utilizzerà un nuovo codice di stato. significato: attualmente 301/302dice al browser di andare e recuperare il contenuto di questa richiesta in una nuova location.

Nello standard esteso, dirà che se la risposta status: 308(solo un esempio), il browser dovrebbe reindirizzare la pagina principale a quella locationfornita.

Detto ciò; Sono propenso a imitare già questo comportamento futuro , e quindi quando è necessario un document.redirect, il server risponde come:

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

Quando JS ottiene il " status: 204", verifica l'esistenza x-status: 308dell'intestazione e fa un document.redirect alla pagina fornita locationnell'intestazione.

Questo ha senso per te?


7

Alcuni potrebbero trovare utile quanto segue:

Volevo che i client fossero reindirizzati alla pagina di accesso per qualsiasi azione di restituzione inviata senza un token di autorizzazione. Poiché tutte le mie azioni di riposo sono basate su Ajax, avevo bisogno di un buon modo generico per reindirizzare alla pagina di accesso invece di gestire la funzione di successo Ajax.

Questo è quello che ho fatto:

Su qualsiasi richiesta Ajax il mio server restituirà una risposta Json 200 "NECESSARIO AUTENTICARE" (se il client deve autenticarsi).

Semplice esempio in Java (lato server):

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

Nel mio Javascript ho aggiunto il seguente codice:

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

E questo è tutto.


5

nel servlet dovresti mettere response.setStatus(response.SC_MOVED_PERMANENTLY); per inviare lo stato "301" xmlHttp necessario per un reindirizzamento ...

e nella funzione $ .ajax non dovresti usare la .toString()funzione ..., solo

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

il problema è che non è molto flessibile, non puoi decidere dove reindirizzare ..

il reindirizzamento attraverso i servlet dovrebbe essere il modo migliore. ma non riesco ancora a trovare il modo giusto per farlo.


5

Volevo solo aggrapparmi a qualsiasi richiesta Ajax per l'intera pagina. @SuperG mi ha fatto iniziare. Ecco cosa ho finito con:

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

Volevo verificare specificamente determinati codici di stato http su cui basare la mia decisione. Tuttavia, puoi semplicemente associarti a ajaxError per ottenere qualcosa di diverso dal successo (solo 200 forse?) Che avrei potuto scrivere:

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

1
quest'ultimo nasconderebbe qualsiasi altro errore che rendesse problematica la risoluzione dei problemi
Tim Abell

1
Un 403 non significa che l'utente non è autenticato, significa che l'utente (probabilmente autenticato) non ha l'autorizzazione per visualizzare la risorsa richiesta. Quindi non dovrebbe reindirizzare alla pagina di accesso
Rob

5

Se vuoi anche passare i valori, puoi anche impostare le variabili di sessione e accedere ad esempio: nel tuo jsp puoi scrivere

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

E poi puoi memorizzare questo valore temporaneo nella tua variabile javascript e giocare


5

Non ho avuto alcun successo con la soluzione dell'intestazione: non sono mai stati raccolti nel mio metodo ajaxSuccess / ajaxComplete. Ho usato la risposta di Steg con la risposta personalizzata, ma ho modificato un po 'il lato JS. Ho impostato un metodo che chiamo in ciascuna funzione in modo da poter utilizzare standard $.gete $.postmetodi.

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

Esempio in uso ...

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

Infine, risolvo il problema aggiungendo un'abitudine HTTP Header. Appena prima della risposta per ogni richiesta sul lato server, aggiungo l'URL richiesto corrente all'intestazione della risposta.

Il mio tipo di applicazione sul server è Asp.Net MVCe ha un buon posto per farlo. in Global.asaxho implementato l' Application_EndRequestevento così:

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

    }

Funziona perfettamente per me! Ora, in ogni risposta della JQuery $.postho richiesta urlaltre intestazioni di risposta e anche che si presenta come risultato del POSTmetodo in base allo stato 302, 303, ....

e un'altra cosa importante è che non è necessario modificare il codice sul lato server né sul lato client.

e la prossima è la possibilità di accedere alle altre informazioni di post azione quali errori, messaggi e ..., In questo modo.

Ho pubblicato questo, forse aiutare qualcuno :)


4

Stavo avendo questo problema su un'app di django con cui sto armeggiando (disclaimer: sto armeggiando per imparare e non sono affatto un esperto). Quello che volevo fare era usare jQuery ajax per inviare una richiesta DELETE a una risorsa, eliminarla sul lato server, quindi inviare un reindirizzamento alla (sostanzialmente) homepage. Quando ho inviato HttpResponseRedirect('/the-redirect/')dallo script Python, il metodo Ajax di jQuery riceveva 200 invece di 302. Quindi, quello che ho fatto è stato inviare una risposta di 300 con:

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

Quindi ho inviato / gestito la richiesta sul client con jQuery.ajax in questo modo:

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

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

Forse usare 300 non è "giusto", ma almeno ha funzionato proprio come volevo.

PS: questo è stato un grande dolore da modificare sulla versione mobile di SO. L'ISP stupido ha inviato la mia richiesta di annullamento del servizio proprio quando ho finito con la mia risposta!


4

Puoi anche agganciare il prototipo di invio XMLHttpRequest. Questo funzionerà per tutti gli invii (jQuery / dojo / etc) con un solo gestore.

Ho scritto questo codice per gestire un errore scaduto di 500 pagine, ma dovrebbe funzionare altrettanto bene per intercettare un reindirizzamento di 200. Prepara la voce di Wikipedia su XMLHttpRequest onreadystatechange sul significato di 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

Inoltre, vorrai probabilmente reindirizzare l'utente all'URL delle intestazioni indicato. Quindi finalmente sarà così:

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

UPD: Opps. Hanno lo stesso compito, ma non funziona. Fare questa roba. Ti mostrerò la soluzione quando la troverò.


4
Questa risposta dovrebbe essere cancellata dall'autore poiché egli stesso afferma che non funziona. Può pubblicare una soluzione funzionante in seguito. -1
TheCrazyProgrammer

L'idea è buona, ma il problema è che l'intestazione "Posizione" non viene mai passata.
Martin Zvarík,

La mia modifica è stata rifiutata, quindi ... devi usare "var xmlHttp = $ .ajax ({...." la variabile non può essere dentro ... quindi usa: console.log (xmlHttp.getAllResponseHeaders ()) ;
Martin Zvarík l'

2

Ho ricevuto un solulion di lavoro usando le risposte dal link @John e @Arpad e @RobWinch link

Uso Spring Security 3.2.9 e jQuery 1.10.2.

Estendi la classe Spring per causare una risposta 4XX solo dalle richieste 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>

Nei miei JSP, aggiungere un gestore di errori AJAX globale come mostrato qui

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

Inoltre, rimuovere i gestori di errori esistenti dalle chiamate AJAX nelle pagine 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");
//            }
        });

Spero che aiuti gli altri.

Update1 Ho scoperto che dovevo aggiungere l'opzione (always-use-default-target = "true") alla configurazione form-login. Ciò era necessario poiché dopo che una richiesta AJAX veniva reindirizzata alla pagina di accesso (a causa della sessione scaduta), Spring ricorda la precedente richiesta AJAX e reindirizza automaticamente ad essa dopo l'accesso. Ciò causa la visualizzazione del JSON restituito nella pagina del browser. Certo, non quello che voglio.

Update2 Invece di utilizzare always-use-default-target="true", utilizzare l'esempio @RobWinch per bloccare le richieste AJAX da requstCache. Ciò consente di reindirizzare i normali collegamenti alla destinazione originale dopo l'accesso, ma AJAX passa alla home page dopo l'accesso.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.