javascript - variable - redireccionar con jquery ajax




Cómo gestionar una solicitud de redireccionamiento después de una llamada jQuery Ajax (20)

Estoy usando $.post() para llamar a un servlet usando Ajax y luego usar el fragmento HTML resultante para reemplazar un elemento div 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 elemento div 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?


Algunos pueden encontrar lo siguiente útil:

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. Como todas mis acciones de descanso están basadas en Ajax, necesitaba una buena forma genérica para 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 he hecho:

En cualquier solicitud de Ajax, mi servidor devolverá una respuesta Json 200 "NECESIDAD DE AUTENTICAR" (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 he añadido 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.


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: Enlazar 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 algunas nuevas especificaciones / encabezados personalizados. Tampoco debería tener que modificar ninguna de sus llamadas ajax existentes.

Edición: el comentario de Per @ Rob a continuación, 401 (el código de estado HTTP para los errores de autenticación) debe ser el indicador. Consulte 403 Respuestas HTTP no autorizadas vs 401 respuestas no autorizadas para obtener más detalles. Dicho esto, algunos marcos web utilizan 403 tanto para errores de autenticación como de autorización, así que adáptelos. Gracias rob


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 'hacky'. Se me ha ocurrido 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 funciona para solicitudes AJAX (ya que los navegadores no ejecutarán el 302 / redirect).

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 401 respuestas para solicitudes AJAX, que su javascript puede manejar al recargar la página. La recarga de 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 ejemplo de javascript para manejar 401 errores:

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

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

Por ejemplo, nuestra función de envoltura era algo así 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 devolvieron 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.


Me gusta el método de Timmerz con un ligero toque de limón. Si alguna vez le devuelven contentType of text / html cuando espera JSON , lo más probable es que lo redirijan. En mi caso, simplemente recargo la página, y se redirige a la página de inicio de sesión. Ah, y verifica que el estado de jqXHR sea 200, lo que parece una tontería, porque estás 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();
    }

});

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

Esto realmente me molesta, y si alguien aquí tiene algo de "atracción" en el W3C, le agradecería que le dijera al W3C que realmente necesitamos manejar los códigos 301 y 302 por nosotros mismos ...! ;)


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

Tuve un problema similar al tuyo. Realizo una solicitud ajax que tiene 2 respuestas posibles: 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.

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

Creo que el nuevo estándar HTTP será usar un nuevo código de estado. es decir, actualmente 301/302 le dice al navegador que vaya a buscar el contenido de esta solicitud a una nueva location .

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

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

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

Cuando JS obtiene el " status: 204 ", verifica la existencia del encabezado x-status: 308 y realiza una redirección de documentos a la página provista en el encabezado de la location .

¿Esto tiene algún sentido para usted?


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

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>

Reuniendo lo que Vladimir Prudnikov y Thomas Hansen dijeron:

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

Si bien las respuestas parecen funcionar para las personas, si está utilizando Spring Security, he encontrado que extender LoginUrlAuthenticationEntryPoint y agregar un código específico para manejar AJAX es más sólido. La mayoría de los ejemplos interceptan todas las redirecciones, no solo las fallas de autenticación. Esto fue indeseable para el proyecto en el que trabajo. Puede encontrar la necesidad de extender ExceptionTranslationFilter y anular el método "sendStartAuthentication" para eliminar el paso de almacenamiento en caché si no desea que la solicitud AJAX fallida esté 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


Solo quería aferrarme a cualquier solicitud de ajax para toda la página. @SuperG me hizo 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 en. Sin embargo, solo puedes unirte a ajaxError para obtener algo que no sea el éxito (¿200 solo quizás?) Podría haber escrito:

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

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 el indexOf para buscar cualquier cadena única que exista en su página de inicio de sesión ...


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á hasta la página de inicio de sesión. "#site" es un id de un div que se encuentra en todas las páginas excepto en la página de inicio de sesión.


en el servlet debes 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 debería usar la función .toString() ..., solo

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

El problema es que no es muy flexible, no puede decidir dónde desea redirigir ...

La redirección a través de los servlets debería ser la mejor manera. Pero todavía no puedo encontrar la manera correcta de hacerlo.


Si también quiere 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 puedes almacenar este valor temporal en tu variable javascript y jugar.


Estaba teniendo este problema en una aplicación de django con la que estoy haciendo pequeños retoques (descargo de responsabilidad: estoy tratando de 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, eliminarla del 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 recibía 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'); 
  } 
});

Tal vez el uso de 300 no es "correcto", pero al menos funcionó como yo quería.

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


Finalmente, resuelvo el problema agregando una costumbre 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 lo 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 he solicitado el urly también otras cabeceras de respuesta que viene como resultado de 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 la siguiente es la capacidad de obtener acceso 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 :)


Obtuve una solución de trabajo utilizando las respuestas de @John y @Arpad .com/a/8426947/4505142 y @RobWinch link

Yo uso Spring Security 3.2.9 y jQuery 1.10.2.

Extienda la clase de Spring para provocar la 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 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");
          }
      }
  });

Además, elimine los manejadores 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 los demás.

Update1 Encontré que necesitaba agregar la opción (always-use-default-target = "true") a la configuración de inicio de sesión de formulario. Esto fue necesario ya que después de que una solicitud de AJAX se redirige a la página de inicio de sesión (debido a una sesión vencida), Spring recuerda la solicitud de AJAX anterior y la redirige automáticamente a ella 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 @RobWinch del bloqueo de solicitudes AJAX desde el requstCache. Esto permite que los enlaces normales se redirijan 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.


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

Escribí este código para manejar un error de 500 páginas caducadas, pero debería funcionar igual de bien para atrapar un redireccionamiento de 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);
}




redirect