Cómo administrar una solicitud de redireccionamiento después de una llamada jQuery Ajax

1454

Estoy usando $.post()para llamar a un servlet usando Ajax y luego usando el fragmento HTML resultante para reemplazar un divelemento en la página actual del usuario. Sin embargo, si la sesión se agota, el servidor envía una directiva de redireccionamiento para enviar al usuario a la página de inicio de sesión. En este caso, jQuery está reemplazando el divelemento con el contenido de la página de inicio de sesión, lo que obliga a los ojos del usuario a presenciar una escena rara.

¿Cómo puedo administrar una directiva de redireccionamiento desde una llamada Ajax con jQuery 1.2.6?

5
  • 1
    (no es una respuesta como tal): he hecho esto en el pasado editando la biblioteca jquery y agregando una marca de verificación para la página de inicio de sesión en cada XHR completo. No es la mejor solución porque tendría que hacerse cada vez que actualiza, pero resuelve el problema. 14/10/08 a las 11:42
  • 1
    Consulte la pregunta relacionada: stackoverflow.com/questions/5941933/…
    Nutel
    6/12/11 a las 21:58
  • El HttpContext.Response.AddHeaderéxito y comprobar en ajaxsetup es el camino a seguir
    LCJ
    12/10/2013 a las 11:51
  • 5
    ¿Por qué el servidor no puede devolver el 401? En ese caso, puede tener un $ .ajaxSetup global y usar el código de estado para redirigir la página.
    Vishal
    18 dic '13 a las 16:41
  • 1
    este enlace doanduyhai.wordpress.com/2012/04/21/… me da la solución correcta 19 de septiembre de 2014 a las 20:16
727

Leí esta pregunta e implementé el enfoque que se ha establecido con respecto a configurar el código de estado HTTP de respuesta en 278 para evitar que el navegador maneje las redirecciones de manera transparente. Aunque esto funcionó, estaba un poco insatisfecho ya que es un truco.

Después de investigar más, abandoné este enfoque y usé JSON . En este caso, todas las respuestas a las solicitudes AJAX tienen el código de estado 200 y el cuerpo de la respuesta contiene un objeto JSON que se construye en el servidor. El JavaScript del cliente puede usar el objeto JSON para decidir qué debe hacer.

Tuve un problema similar al tuyo. Realizo una solicitud AJAX que tiene 2 posibles respuestas: una que redirige el navegador a una nueva página y otra que reemplaza un formulario HTML existente en la página actual por uno nuevo. El código jQuery para hacer esto se parece a:

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

Los "datos" del objeto JSON se construyen en el servidor para tener 2 miembros: data.redirecty data.form. Encontré que este enfoque es mucho mejor.

15
  • 63
    Como se indica en la solución en stackoverflow.com/questions/503093/… es mejor usar window.location.replace (data.redirect); que window.location.href = data.redirect; 17/12/10 a las 13:05
  • 8
    Cualquier motivo por el que no sería mejor utilizar códigos HTTP en la acción. Por ejemplo, ¿un código 307 que es redireccionamiento temporal HTTP? 24 feb 2011 a las 19:06
  • 18
    @Sergei Golos la razón es que si realiza una redirección HTTP, la redirección en realidad nunca llega a la devolución de llamada exitosa de ajax. El navegador procesa la redirección entregando un código 200 con el contenido del destino de la redirección. 3 de mayo de 2011 a las 3:08
  • 2
    ¿Cómo se vería el código del servidor? para tener un valor de retorno de data.form y data.redirect. Básicamente, ¿cómo puedo determinar si le pongo una redirección?
    Vnge
    8 de diciembre de 2013 a las 1:47
  • 6
    Esta respuesta sería más útil si hubiera mostrado cómo se hace esto en el servidor. 22/10/15 a las 14:01
260

Resolví este problema por:

  1. Agregar un encabezado personalizado a la respuesta:

    public ActionResult Index(){
        if (!HttpContext.User.Identity.IsAuthenticated)
        {
            HttpContext.Response.AddHeader("REQUIRES_AUTH","1");
        }
        return View();
    }
    
  2. Vincular una función de JavaScript al ajaxSuccessevento y verificar si el encabezado existe:

    $(document).ajaxSuccess(function(event, request, settings) {
        if (request.getResponseHeader('REQUIRES_AUTH') === '1') {
           window.location = '/';
        }
    });
    
9
  • 6
    Qué maravillosa solución. Me gusta la idea de una solución integral. Necesito verificar el estado 403, pero puedo usar el enlace ajaxSuccess en el cuerpo para esto (que es lo que realmente estaba buscando). Gracias. 4 de agosto de 2010 a las 17:21
  • 4
    Acabo de hacer esto y descubrí que necesitaba ajaxComplete donde estaba usando la función $ .get () y cualquier estado que no fuera 200 no se activaba. De hecho, probablemente podría haberme enlazado a ajaxError en su lugar. Vea mi respuesta a continuación para obtener más detalles. 4 de agosto de 2010 a las 18:42
  • 2
    Está bien en muchos casos, pero ¿qué pasa si su marco maneja la autorización? 18/06/12 a las 7:11
  • 13
    Me gusta el enfoque del encabezado, pero también creo, como @ mwoods79, que el conocimiento de a dónde redireccionar no debe duplicarse. Lo resolví agregando un encabezado REDIRECT_LOCATION en lugar de un booleano. 24/11/12 a las 16:40
  • 3
    Tenga cuidado de establecer el encabezado en la respuesta DESPUÉS de la redirección. Como se describe en otras respuestas en esta página, la redirección puede ser transparente para el controlador ajaxSucces. Por lo tanto, incluí el encabezado en la respuesta GET de la página de inicio de sesión (que finalmente y solo desencadenó ajaxSuccess en mi escenario).
    sieppl
    18 feb 2013 a las 14:35
122

Ningún navegador maneja correctamente las respuestas 301 y 302. Y, de hecho, el estándar incluso dice que deberían manejarlos de manera "transparente", lo que es un dolor de cabeza MASIVO para los proveedores de bibliotecas Ajax. En Ra-Ajax nos vimos obligados a usar el código de estado de respuesta HTTP 278 (solo un código de éxito "no utilizado") para manejar de forma transparente las redirecciones desde el servidor ...

Esto realmente me molesta, y si alguien aquí tiene algo de "tirón" en el W3C le agradecería que usted podría dejar W3C conocimientos que realmente necesitamos para manejar 301 y 302 códigos de nosotros mismos ...! ;)

9
  • 3
    Por un lado, creo que 278 debería convertirse en parte de la especificación HTTP oficial. 7/06/12 a las 21:51
  • 2
    ¿No los maneja ya de forma transparente? Si un recurso se ha movido, manejarlo de manera transparente significa repetir la solicitud en la URL proporcionada. Eso es lo que esperaría usar la API XMLHttpRequest. 12/0913 a las 21:27
  • @ PhilippeRathé De acuerdo. El manejo transparente es justo lo que quiero. Y no sé por qué se considera malo. 6 de enero de 2016 a las 13:19
  • @smwikipedia Para organizar una redirección del marcado en la sección principal sin redirigir la página.
    cmc
    6 de junio de 2017 a las 12:39
  • 1
    Los navegadores lo manejan de forma transparente desde el primer día. ¿Qué es una solicitud ajax? Es cuando desea que un navegador haga una solicitud y devuelva el resultado. Lo que desea es un caso especial: "... devuelva el resultado, pero en caso de una redirección no devuelva el resultado". Eso podría molestarlo como desarrollador de bibliotecas, porque está tratando de manejar el caso general, cambiar la forma en que funcionan los navegadores. Pero como lo hace, debe esperar molestias.
    x-yuri
    12 de marzo a las 13:07
102

La solución que finalmente se implementó fue utilizar un contenedor para la función de devolución de llamada de la llamada Ajax y, en este contenedor, verificar la existencia de un elemento específico en el fragmento HTML devuelto. Si se encontró el elemento, el contenedor ejecutó una redirección. De lo contrario, el contenedor reenvió la llamada a la función de devolución de llamada real.

Por ejemplo, nuestra función contenedora era algo como:

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

Luego, al hacer la llamada Ajax usamos algo como:

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

Esto funcionó para nosotros porque todas las llamadas Ajax siempre devolvían HTML dentro de un elemento DIV que usamos para reemplazar una parte de la página. Además, solo necesitábamos redirigir a la página de inicio de sesión.

2
  • 4
    Tenga en cuenta que esto se puede acortar a function cbWrapper (funct) {return function (data) {if ($ ("# myForm", data) .size ()> 0) top.location.href = "login"; else funct (datos); }}. Entonces solo necesita cbWrapper (myActualCB) al llamar a .post. Sí, el código en los comentarios es un desastre, pero debe tenerse en cuenta :) 18/11/10 a las 3:02
  • el tamaño se deprecia, por lo que puede usar .length aquí en lugar del tamaño
    sunil
    20/07/2016 a las 10:46
74

Me gusta el método de Timmerz con un toque de limón. Si alguna vez obtiene contentType devuelto de texto / html cuando espera JSON , lo más probable es que se le redirija. En mi caso, simplemente recargo la página y se redirige a la página de inicio de sesión. Ah, y compruebe que el estado de jqXHR es 200, lo que parece una tontería, porque está en la función de error, ¿verdad? De lo contrario, los casos de error legítimos forzarán una recarga iterativa (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();
    }

});
2
  • 1
    muchas gracias Brian, tu respuesta fue la mejor para mi escenario, aunque me gustaría que hubiera una verificación más segura, como comparar a qué URL / página se está redireccionando en lugar de la simple verificación de "tipo de contenido". No pude encontrar a qué página se está redirigiendo desde el objeto jqXHR.
    Johnny
    20/04/12 a las 20:32
  • Verifiqué el estado 401 y luego lo redireccioné. Funciona como un campeón.
    Eric
    23/07/2015 a las 20:41
58

Utilice la $.ajax()llamada de bajo nivel :

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

Pruebe esto para una redirección:

if (xmlHttp.code != 200) {
  top.location.href = '/some/other/page';
}
8
  • Estaba tratando de evitar las cosas de bajo nivel. De todos modos, suponga que uso algo como lo que describe, ¿cómo fuerzo una redirección del navegador una vez que detecto que el código HTTP es 3xx? Mi objetivo es redirigir al usuario, no solo anunciar que su sesión ha expirado. 14/108 a las 13:25
  • 4
    Por cierto, $ .ajax () no es de muy, muy bajo nivel. Es de bajo nivel en términos de jQuery porque hay $ .get, $ .post, etc. que son mucho más simples que $ .ajax y todas sus opciones.
    Till
    14/108 a las 13:31
  • 17
    ¡Oh chico! Lo siento, tuve que "anular la aceptación" de tu respuesta, sigue siendo muy útil. La cuestión es que las redirecciones son administradas automáticamente por XMLHttpRequest, por lo tanto, SIEMPRE obtengo un código de estado 200 después de la redirección (¡suspiro!). Creo que tendré que hacer algo desagradable como analizar el HTML y buscar un marcador. 14 de oct de 2008 a las 15:19
  • 1
    Solo por curiosidad, si la sesión termina en el servidor, ¿no significa eso que el servidor envía un SESSIONID diferente? ¿No podríamos simplemente detectar eso? 16 de diciembre de 2008 a las 1:35
  • 10
    NOTA: Esto no funciona para redireccionamientos. ajax irá a la nueva página y devolverá su código de estado.
    user34537
    14 de marzo de 2010 a las 4:34
34

Solo quería compartir mi enfoque, ya que esto podría ayudar a alguien:

Básicamente incluí un módulo de JavaScript que maneja las cosas de autenticación como mostrar el nombre de usuario y también este caso maneja la redirección a la página de inicio de sesión .

Mi escenario: Básicamente tenemos un servidor ISA entre el cual escucha todas las solicitudes y responde con un 302 y un encabezado de ubicación a nuestra página de inicio de sesión.

En mi módulo de JavaScript, mi enfoque inicial fue algo así como

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

El problema (como muchos ya se mencionaron aquí) es que el navegador maneja la redirección por sí mismo, por lo ajaxCompleteque nunca se llamó a mi devolución de llamada, sino que obtuve la respuesta de la página de inicio de sesión ya redirigida que obviamente era una status 200. El problema: ¿cómo detecta si la respuesta 200 exitosa es su página de inicio de sesión real o simplemente alguna otra página arbitraria?

La solución

Como no pude capturar las respuestas de redireccionamiento 302, agregué un LoginPageencabezado en mi página de inicio de sesión que contenía la URL de la página de inicio de sesión. En el módulo, ahora escucho el encabezado y hago una redirección:

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

... y eso funciona a las mil maravillas :). Quizás se pregunte por qué incluyo la URL en el LoginPageencabezado ... bueno, básicamente porque no encontré forma de determinar la URL GETresultante de la redirección automática de ubicación del xhrobjeto ...

3
  • +1, pero se supone que los encabezados personalizados deben comenzar con X-, por lo que sería mejor usar un encabezado X-LoginPage: http://example.com/login. 14/10/12 a las 4:01
  • 6
    @ShaquinTrifonoff Ya no. No utilicé el prefijo X- porque en junio de 2011 un documento ITEF propuso su desaprobación y, de hecho, con junio de 2012 no es oficial que los encabezados personalizados ya no deberían tener prefijos X-.
    Juri
    14/10/12 a las 7:31
  • También tenemos un servidor ISA y me encontré con el mismo problema. En lugar de solucionarlo en el código, usamos las instrucciones en kb2596444 para configurar ISA para que deje de redireccionar. 1 de agosto de 2013 a las 21:44
30

Sé que este tema es antiguo, pero daré otro enfoque que encontré y describí anteriormente aquí . Básicamente, estoy usando ASP.MVC con WIF (pero esto no es realmente importante para el contexto de este tema; la respuesta es adecuada sin importar qué marcos se utilicen. La pista permanece sin cambios: se trata de problemas relacionados con fallas de autenticación mientras se realizan solicitudes ajax ) .

El enfoque que se muestra a continuación se puede aplicar a todas las solicitudes ajax listas para usar (si no redefinen el evento beforeSend, obviamente).

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

Antes de que se realice cualquier solicitud ajax CheckPulse, se invoca el método (el método del controlador, que puede ser cualquier cosa más simple):

[Authorize]
public virtual void CheckPulse() {}

Si el usuario no está autenticado (el token ha caducado) no se puede acceder a dicho método (protegido por Authorizeatributo). Debido a que el marco maneja la autenticación, mientras que el token expira, coloca el estado http 302 en la respuesta. Si no desea que su navegador maneje la respuesta 302 de forma transparente, descárguela en Global.asax y cambie el estado de la respuesta, por ejemplo a 200 OK. Además, agregue el encabezado, que le indica que procese dicha respuesta de manera especial (más adelante en el lado del cliente):

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

Finalmente, en el lado del cliente, verifique dicho encabezado personalizado. Si está presente, se debe realizar una redirección completa a la página de inicio de sesión (en mi caso, window.locationse reemplaza por la URL de la solicitud que es manejada automáticamente por mi marco).

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;
                }
            }
    });
}
3
  • Solucioné el problema utilizando el evento PostAuthenticateRequest en lugar del evento EndRequest. 28/10/2013 a las 16:38
  • @JaroslawWaliszko ¡Pegué el evento equivocado en mi última respuesta! Me refiero al evento PreSendRequestHeaders ... ¡no PostAuthenticateRequest! >> rubor << gracias por señalar mi error. 28/10/2013 a las 21:15
  • @JaroslawWaliszko cuando usa WIF también puede devolver respuestas 401 para solicitudes AJAX y dejar que su javascript las maneje. También está asumiendo que todos los 302 requieren autenticación, lo que podría no ser cierto en todos los casos. Agregué una respuesta si alguien está interesado.
    Rob
    14/0214 a las 14:01
29

Creo que una mejor manera de manejar esto es aprovechar los códigos de respuesta del protocolo HTTP existentes, específicamente 401 Unauthorized.

Así es como lo resolví:

  1. Lado del servidor: si la sesión expira y la solicitud es ajax. enviar un encabezado de código de respuesta 401
  2. Lado del cliente: enlace a los eventos 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';
    }
    });
    

En mi opinión, esto es más genérico y no está escribiendo una nueva especificación / encabezado personalizado. Tampoco debería tener que modificar ninguna de sus llamadas ajax existentes.

Editar: Según el comentario de @ Rob a continuación, 401 (el código de estado HTTP para errores de autenticación) debería ser el indicador. Consulte Respuestas HTTP 403 prohibidas frente a 401 no autorizadas para obtener más detalles. Dicho esto, algunos frameworks web usan 403 tanto para autenticación como para errores de autorización, así que adáptese en consecuencia. Gracias Rob.

2
  • Yo utilizo el mismo enfoque. ¿JQuery realmente llama a ajaxSuccess en el código de error 403? Creo que solo se necesita la parte ajaxError 14/07/2013 a las 21:14
  • trabajando para mi .. Gracias 15/12/20 a las 11:53
25

Resolví este problema así:

Agregue un middleware para procesar la respuesta, si es un redireccionamiento para una solicitud ajax, cambie la respuesta a una respuesta normal con la URL de redireccionamiento.

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

Luego, en ajaxComplete, si la respuesta contiene redirección, debe ser una redirección, así que cambie la ubicación del navegador.

$('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;
       }
   }
}
0
21

Otra solución que encontré (especialmente útil si desea establecer un comportamiento global) es usar el $.ajaxsetup()método junto con la statusCodepropiedad . Como otros señalaron, no use un código de estado de redireccionamiento ( 3xx), en su lugar use un 4xxcódigo de estado y maneje el lado del cliente de redireccionamiento.

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

Reemplace 400con el código de estado que desea manejar. Como ya se mencionó, 401 Unauthorizedpodría ser una buena idea. Yo uso el 400ya que es muy poco específico y puedo usarlo 401para casos más específicos (como credenciales de inicio de sesión incorrectas). Entonces, en lugar de redirigir directamente, su backend debería devolver un 4xxcódigo de error cuando se agote el tiempo de espera de la sesión y usted maneje la redirección del lado del cliente. Funciona perfecto para mí incluso con marcos como backbone.js

2
  • ¿Dónde mencionar la función en la página?
    Vikrant
    27 de enero de 2017 a las 7:43
  • @Vikrant Si entiendo su pregunta correctamente, puede llamar a la función justo después de que se cargue jQuery y antes de realizar sus solicitudes reales. 27 ene 2017 a las 16:52
20

Tengo una solución simple que funciona para mí, no es necesario cambiar el código del servidor ... solo agregue una cucharadita de nuez moscada ...

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

Verifico la presencia de la etiqueta html, pero puede cambiar indexOf para buscar cualquier cadena única que exista en su página de inicio de sesión ...

2
  • Esto no parece funcionar para mí, sigue llamando a la función definida con la llamada ajax, es como si no anulara el método de éxito. 24 de agosto de 2011 a las 6:49
  • No parece funcionar para mí, al menos ya. Posible explicación aquí: stackoverflow.com/a/12010724/260665 6 de mayo de 2020 a las 14:10
20

La mayoría de las soluciones proporcionadas utilizan una solución alternativa, utilizando un encabezado adicional o un código HTTP inadecuado. Esas soluciones probablemente funcionarán, pero se sentirán un poco "hack". Se me ocurrió otra solución.

Estamos usando WIF que está configurado para redirigir (passiveRedirectEnabled = "true") en una respuesta 401. La redirección es útil cuando se manejan solicitudes normales, pero no funcionará para solicitudes AJAX (ya que los navegadores no ejecutarán la redirección 302).

Usando el siguiente código en su global.asax, puede deshabilitar la redirección para solicitudes 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;
        }
    }

Esto le permite devolver respuestas 401 para solicitudes AJAX, que su javascript puede manejar al volver a cargar la página. Recargar la página arrojará un 401 que será manejado por WIF (y WIF redirigirá al usuario a la página de inicio de sesión).

Un javascript de ejemplo para manejar errores 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);
    }
});
1
  • Buena solución. Gracias.
    DanMan
    20/11/18 a las 14:04
19

Este problema puede aparecer luego usando el método ASP.NET MVC RedirectToAction. Para evitar que el formulario muestre la respuesta en div, simplemente puede hacer algún tipo de filtro de respuesta ajax para las respuestas entrantes con $ .ajaxSetup . Si la respuesta contiene la redirección MVC, puede evaluar esta expresión en el lado de JS. Código de ejemplo para JS a continuación:

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

Si los datos son: "window.location = '/ Acount / Login'", el filtro anterior lo detectará y evaluará para realizar la redirección en lugar de permitir que se muestren los datos.

1
  • data está en el cuerpo de la respuesta o en el encabezado?
    Sam YC
    12 de mayo de 2017 a las 2:10
17

Juntando lo que dijeron Vladimir Prudnikov y Thomas Hansen:

  • Cambie su código del lado del servidor para detectar si es un XHR. Si es así, establezca el código de respuesta de la redirección en 278. En django:
   if request.is_ajax():
      response.status_code = 278

Esto hace que el navegador trate la respuesta como un éxito y se la entregue a su Javascript.

  • En su JS, asegúrese de que el envío del formulario sea a través de Ajax, verifique el código de respuesta y redirija si es necesario:
$('#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

Tratar

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

Ponlo en la página de inicio de sesión. Si se cargó en un div en la página principal, se redirigirá a la página de inicio de sesión. "#site" es una identificación de un div que se encuentra en todas las páginas excepto en la página de inicio de sesión.

12

Si bien las respuestas parecen funcionar para las personas si está usando Spring Security, he descubierto que extendiendo LoginUrlAuthenticationEntryPoint y agregando código específico para manejar AJAX de manera más robusta. La mayoría de los ejemplos interceptan todas las redirecciones, no solo las fallas de autenticación. Esto era indeseable para el proyecto en el que trabajo. Es posible que también necesite extender ExceptionTranslationFilter y anular el método "sendStartAuthentication" para eliminar el paso de almacenamiento en caché si no desea que la solicitud AJAX fallida se guarde en caché.

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

Fuentes: 1 , 2

4
  • 3
    Sería útil (para mí) si los votantes en contra me explicaran por qué están votando en contra. Si hay algo malo con esta solución, me gustaría aprender de mis errores. Gracias.
    John
    12 dic '13 a las 20:50
  • Si usa Spring y también JSF, entonces también verifique esto: ("parcial / ajax"). EqualsIgnoreCase (request.getHeader ("faces-request"));
    J Slick
    24 mar 2014 a las 21:01
  • Los usuarios pueden haber votado en contra porque no mencionaste: (1) las modificaciones del lado del cliente necesarias para detectar tu respuesta de error; (2) las modificaciones necesarias para la configuración de Spring para agregar su LoginUrlAuthenticationEntryPoint personalizado a la cadena de filtros.
    J Slick
    24 mar 2014 a las 21:08
  • Tu respuesta es similar a esta de @Arpad. Funcionó para mí; utilizando Spring Security 3.2.9. stackoverflow.com/a/8426947/4505142 21/03/18 a las 20:47
11

Resolví esto poniendo lo siguiente en mi página login.php.

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

Permítanme citar nuevamente el problema como lo describe @Steg

I had a similar problem to yours. I perform an ajax request that has 2 possible responses: one that redirects the browser to a new page and one that replaces an existing HTML form on the current page with a new one.

En mi humilde opinión, este es un verdadero desafío y tendrá que extenderse oficialmente a los estándares HTTP actuales.

Creo que el nuevo estándar Http será utilizar un nuevo código de estado. significado: actualmente 301/302le dice al navegador que vaya a buscar el contenido de esta solicitud a un nuevo location.

En el estándar extendido, dirá que si la respuesta status: 308(solo un ejemplo), entonces el navegador debe redirigir la página principal a la locationproporcionada.

Habiendo dicho eso; Me inclino a imitar este comportamiento futuro y, por lo tanto, cuando se necesita un document.redirect, hago que el servidor responda como:

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

Cuando JS obtiene el " status: 204", comprueba la existencia del x-status: 308encabezado y realiza un document.redirect a la página proporcionada en el locationencabezado.

¿Esto tiene algún sentido para usted?

7

Algunos pueden encontrar útil lo siguiente:

Quería que los clientes fueran redirigidos a la página de inicio de sesión para cualquier acción de descanso que se envíe sin un token de autorización. Dado que todas mis acciones de descanso están basadas en Ajax, necesitaba una buena forma genérica de redirigir a la página de inicio de sesión en lugar de manejar la función de éxito de Ajax.

Esto es lo que hice:

En cualquier solicitud de Ajax, mi servidor devolverá una respuesta Json 200 "NECESITA AUTENTIFICARSE" (si el cliente necesita autenticarse).

Ejemplo simple en Java (lado del servidor):

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

En mi Javascript agregué el siguiente código:

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

Y eso es todo.

5

en el servlet debe poner response.setStatus(response.SC_MOVED_PERMANENTLY); para enviar el estado xmlHttp '301' que necesita para una redirección ...

y en la función $ .ajax no debes usar la .toString()función ..., solo

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

el problema es que no es muy flexible, no puedes decidir a dónde quieres redireccionar.

Redirigir a través de los servlets debería ser la mejor manera. pero todavía no puedo encontrar la manera correcta de hacerlo.

5

Solo quería aferrarme a cualquier solicitud ajax para toda la página. @SuperG me ayudó a empezar. Esto es lo que terminé 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;
        }
});

Quería verificar específicamente ciertos códigos de estado http para basar mi decisión. Sin embargo, puede enlazar a ajaxError para obtener cualquier otra cosa que no sea el éxito (¿solo 200 quizás?). Podría haber escrito:

$('body').bind('ajaxError', function(event,request,settings){
    window.location.replace("http://mysite.tld/login");
}
2
  • 1
    este último ocultaría cualquier otro error que dificulte la resolución de problemas 8 de agosto de 2011 a las 10:41
  • 1
    Un 403 no significa que el usuario no esté autenticado, significa que el usuario (probablemente autenticado) no tiene permiso para ver el recurso solicitado. Por lo tanto, no debería redirigir a la página de inicio de sesión.
    Rob
    14 feb 2014 a las 13:37
5

Si también desea pasar los valores, también puede configurar las variables de sesión y acceder, por ejemplo: En su jsp puede escribir

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

Y luego puede almacenar este valor de temperatura en su variable de JavaScript y jugar

5

No tuve ningún éxito con la solución de encabezado; nunca se recogieron en mi método ajaxSuccess / ajaxComplete. Usé la respuesta de Steg con la respuesta personalizada, pero modifiqué un poco el lado JS. Configuro un método que llamo en cada función para poder usar métodos estándar $.gety $.post.

function handleAjaxResponse(data, callback) {
    //Try to convert and parse object
    try {
        if (jQuery.type(data) === "string") {
            data = jQuery.parseJSON(data);
        }
        if (data.error) {
            if (data.error == 'login') {
                window.location.reload();
                return;
            }
            else if (data.error.length > 0) {
                alert(data.error);
                return;
            }
        }
    }
    catch(ex) { }

    if (callback) {
        callback(data);
    }
}

Ejemplo de ello en 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

Finalmente, resuelvo el problema agregando un archivo personalizado HTTP Header. Justo antes de la respuesta para cada solicitud en el lado del servidor, agrego la URL solicitada actual al encabezado de la respuesta.

Mi tipo de aplicación en el servidor es Asp.Net MVC, y tiene un buen lugar para hacerlo. en que Global.asaximplementé el Application_EndRequestevento así:

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

    }

¡Funciona perfecto para mí! Ahora en cada respuesta de la JQuery $.postque tengo los solicitados urlotras cabeceras de respuesta y también que viene como resultado del POSTmétodo por el estado 302, 303, ....

y otra cosa importante es que no es necesario modificar el código del lado del servidor ni del lado del cliente.

y el siguiente es la capacidad de acceder a la otra información de la acción posterior, tales como errores, mensajes y ..., de esta manera.

Publiqué esto, tal vez ayude a alguien :)

4

Estaba teniendo este problema en una aplicación de django con la que estoy jugando (descargo de responsabilidad: estoy jugando para aprender y de ninguna manera soy un experto). Lo que quería hacer era usar jQuery ajax para enviar una solicitud DELETE a un recurso, eliminarlo en el lado del servidor y luego enviar una redirección a (básicamente) la página de inicio. Cuando envié HttpResponseRedirect('/the-redirect/')desde el script de Python, el método ajax de jQuery estaba recibiendo 200 en lugar de 302. Entonces, lo que hice fue enviar una respuesta de 300 con:

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

Luego envié / manejé la solicitud en el cliente con jQuery.ajax así:

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

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

Quizás usar 300 no sea "correcto", pero al menos funcionó como yo quería.

PD: fue un gran dolor editarlo en la versión móvil de SO. ¡El estúpido ISP pasó mi solicitud de cancelación de servicio justo cuando terminé con mi respuesta!

4

También puede enlazar XMLHttpRequest send prototype. Esto funcionará para todos los envíos (jQuery / dojo / etc) con un controlador.

Escribí este código para manejar un error de vencimiento de 500 páginas, pero debería funcionar igual de bien para atrapar un redireccionamiento 200. Prepare la entrada de wikipedia en XMLHttpRequest onreadystatechange sobre el significado de 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

Obtuve una solución de trabajo usando las respuestas de @John y @Arpad link y @RobWinch link

Yo uso Spring Security 3.2.9 y jQuery 1.10.2.

Extienda la clase de Spring para provocar una respuesta 4XX solo de las solicitudes 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>

En mis JSP, agregue un controlador de errores AJAX global como se muestra aquí

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

Además, elimine los controladores de errores existentes de las llamadas AJAX en las páginas 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");
//            }
        });

Espero que ayude a otros.

Update1 Descubrí que necesitaba agregar la opción (always-use-default-target = "true") a la configuración de inicio de sesión del formulario. Esto era necesario ya que después de que una solicitud AJAX se redirige a la página de inicio de sesión (debido a una sesión caducada), Spring recuerda la solicitud AJAX anterior y la redirecciona automáticamente después de iniciar sesión. Esto hace que el JSON devuelto se muestre en la página del navegador. Por supuesto, no es lo que quiero.

Update2 En lugar de usar always-use-default-target="true", use el ejemplo de @RobWinch para bloquear solicitudes AJAX desde requstCache. Esto permite que los enlaces normales sean redirigidos a su destino original después de iniciar sesión, pero AJAX va a la página de inicio después de iniciar sesión.

0

Utilice la opción statusCode como en el caso siguiente, los redireccionamientos suelen ser códigos de estado 301, 302 para redireccionamientos.

$.ajax({
    type: <HTTP_METHOD>,
    url:  {server.url},
    data: {someData: true},
    statusCode: {
        301: function(responseObject, textStatus, errorThrown) {
            //yor code goes here
        },
        302: function(responseObject, textStatus, errorThrown) {
            //yor code goes here
        }           
    }
})
.done(function(data){
    alert(data);
})
.fail(function(jqXHR, textStatus){
    alert('Something went wrong: ' + textStatus);
})
.always(function(jqXHR, textStatus) {
   alert('Ajax request was finished')
});
7
  • ¿Qué versión de jquery admite la opción statusCode? 23/12/20 a las 10:13
  • está ahí desde la versión 1.5, supongo, api.jquery.com/jquery.ajax
    sumit
    1 de enero a las 19:03
  • De acuerdo, de todos modos estaba buscando una redirección del lado del servidor, para la cual el código de estado será 200 18 de enero a las 5:39
  • ¿En qué servidor el código de redireccionamiento es 200?
    sumit
    22 de enero a las 6:09
  • en el servidor de matraces de Python 22 de enero a las 7:14