javascript - Come gestire una richiesta di reindirizzamento dopo una chiamata Ajax jQuery



15 Answers

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. ajaxSuccess una funzione JavaScript all'evento ajaxSuccess e verifica se l'intestazione esiste:

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

Sto usando $.post() per chiamare un servlet usando Ajax e quindi usando il frammento HTML risultante per sostituire un elemento div 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 sostituisce l'elemento div con i contenuti della pagina di accesso, costringendo gli occhi dell'utente a testimoniare una scena rara.

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




La soluzione che è stata infine implementata era 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 eseguiva un reindirizzamento. In caso contrario, il wrapper ha inoltrato la chiamata alla funzione di callback 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, durante 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 l'HTML all'interno di un elemento DIV che usiamo per sostituire un pezzo della pagina. Inoltre, abbiamo solo bisogno di reindirizzare alla pagina di accesso.




Utilizza la chiamata $.ajax() 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';
}



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

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

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

Prima di ogni richiesta CheckPulse viene eseguito il metodo CheckPulse (il metodo controller che può essere qualsiasi cosa più semplice):

[Authorize]
public virtual void CheckPulse() {}

Se l'utente non è autenticato (il token è scaduto) non è possibile accedere a tale metodo (protetto da attributo Authorize ). Poiché il framework gestisce l'autenticazione, mentre il token scade, inserisce lo stato http 302 nella risposta. Se non si desidera che il browser gestisca in modo trasparente la risposta 302, acquisirla in Global.asax e modificare lo stato della risposta, ad esempio 200 OK. Inoltre, aggiungi l'intestazione, che ti indica di elaborare tale risposta in modo speciale (più avanti 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, dal lato client, controlla l'intestazione personalizzata. Se presente, deve essere eseguito il reindirizzamento completo alla pagina di accesso (nel mio caso window.location viene sostituito window.location dalla 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 questo problema in questo modo:

Aggiungere un middleware per elaborare la risposta, se si tratta di un reindirizzamento per una richiesta Ajax, modificare la risposta a 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 modificare 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;
       }
   }
}



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

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

Sostituisci 400 con lo statuscode che vuoi gestire. Come già accennato 401 Unauthorized potrebbe essere una buona idea. Io uso il 400 dal momento che è molto aspecifico e posso usare il 401 per casi più specifici (come le credenziali di accesso errate). Quindi, invece di reindirizzare direttamente il tuo backend dovrebbe restituire un codice di errore 4xx quando la sessione è scaduta e tu gestisci il reindirizzamento lato client. Funziona perfettamente per me anche con framework come backbone.js




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

  • Cambia il tuo codice lato server per rilevare se si tratta di un XHR. Se lo è, imposta il codice di risposta del reindirizzamento su 278. In django:
   if request.is_ajax():
      response.status_code = 278

Questo fa sì che il browser tratti la risposta come un successo e la consegna 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); 
});



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, verrà reindirizzato alla pagina di accesso. "#site" è un ID di un div che si trova su tutte le pagine ad eccezione della pagina di accesso.




Sebbene le risposte sembrino funzionare per le persone se si sta utilizzando Spring Security, ho trovato l'estensione di LoginUrlAuthenticationEntryPoint e l'aggiunta di codice specifico per gestire AJAX più robusto. La maggior parte degli esempi intercetta tutti i reindirizzamenti, non solo quelli di autenticazione. Questo non era auspicabile per il progetto su cui lavoro. È possibile che sia necessario estendere ExceptionTranslationFilter e sovrascrivere il metodo "sendStartAuthentication" per rimuovere il passaggio di memorizzazione nella cache se non si desidera memorizzare la richiesta AJAX memorizzata nella cache.

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




Alcuni potrebbero trovare il sotto utile:

Volevo che i client venissero reindirizzati alla pagina di login per ogni rest-action che viene inviata senza un token di autorizzazione. Poiché tutte le mie rest-action sono basate su Ajax, avevo bisogno di un buon modo generico per reindirizzare alla pagina di login 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 "NECESSITÀ DI 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.




nel servlet dovresti inserire response.setStatus(response.SC_MOVED_PERMANENTLY); per inviare lo stato xmlHttp '301' di cui hai bisogno per un reindirizzamento ...

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

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

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

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




Se vuoi anche passare i valori, puoi anche impostare le variabili di sessione e accedere ad Eg: 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




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 MVC, e 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 la richiesta urle anche altri intestazioni di risposta, che si presenta come risultato di POSTmetodo in base allo stato 302, 303, ....

e altra cosa importante è che non è necessario modificare il codice lato server o lato client.

e la prossima è la possibilità di ottenere l'accesso alle altre informazioni di post azione come errori, messaggi e ..., In questo modo.

Ho postato questo, forse aiutare qualcuno :)




È anche possibile collegare il prototipo di invio XMLHttpRequest. Questo funzionerà per tutte le mandate (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 intrappolare un reindirizzamento 200. Pronta la voce 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);
}



Ho ottenuto un solulion di lavoro utilizzando le risposte da @ John e @Arpad .com/a/8426947/4505142 e @RobWinch link

Io uso Spring Security 3.2.9 e jQuery 1.10.2.

Estendi la classe di Spring per causare la 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, aggiungi un gestore di errori AJAX globale come mostrato here

  $( 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, rimuovi 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 avevo bisogno di aggiungere l'opzione (always-use-default-target = "true") alla configurazione di login form. Ciò era necessario poiché dopo che una richiesta AJAX viene 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ò fa sì che il JSON restituito venga visualizzato nella pagina del browser. Certo, non quello che voglio.

Update2 Invece di usare always-use-default-target="true", usa @RobWinch esempio di blocco delle richieste AJAX da requstCache. Ciò consente ai collegamenti normali di essere reindirizzati al loro target originale dopo il login, ma AJAX va alla home page dopo il login.




Related