javascript - success - redirection jquery ajax




Comment gérer une demande de redirection après un appel jQuery Ajax (20)

Alors que les réponses semblent fonctionner pour les gens si vous utilisez Spring Security, j'ai trouvé l'extension LoginUrlAuthenticationEntryPoint et l'ajout de code spécifique pour gérer AJAX plus robuste. La plupart des exemples interceptent toutes les redirections, pas seulement les échecs d'authentification. Ce n'était pas souhaitable pour le projet sur lequel je travaille. Vous devrez peut-être également étendre ExceptionTranslationFilter et remplacer la méthode "sendStartAuthentication" pour supprimer l'étape de mise en cache si vous ne souhaitez pas que la requête AJAX échouée soit mise en cache.

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

}

Sources: 1 , 2

J'utilise $.post() pour appeler une servlet en utilisant Ajax, puis en utilisant le fragment HTML résultant pour remplacer un élément div dans la page actuelle de l'utilisateur. Cependant, si la session expire, le serveur envoie une directive de redirection pour envoyer l'utilisateur à la page de connexion. Dans ce cas, jQuery remplace l'élément div par le contenu de la page de connexion, forçant ainsi les yeux de l'utilisateur à assister à une scène rare.

Comment puis-je gérer une directive de redirection à partir d'un appel Ajax avec jQuery 1.2.6?


Aucun navigateur ne gère correctement les réponses 301 et 302. Et en fait, la norme dit même qu'ils devraient les traiter «de manière transparente», ce qui est un casse-tête MASSIVE pour les fournisseurs de la bibliothèque Ajax. Dans Ra-Ajax nous avons été contraints d'utiliser le code d'état de réponse HTTP 278 (juste un code de succès "inutilisé") pour gérer de manière transparente les redirections du serveur ...

Cela m'énerve vraiment, et si quelqu'un ici a un peu de "pull" dans le W3C, j'apprécierais que vous puissiez faire savoir au W3C que nous avons vraiment besoin de manipuler les codes 301 et 302 nous-mêmes ...! ;)


Cela a fonctionné pour moi:

success: function(data, textStatus, xhr) {

        console.log(xhr.status);
}

en cas de succès, ajax obtiendra le même code d'état que le navigateur reçoit du serveur et l'exécutera.


Certains pourraient trouver le ci-dessous utile:

Je voulais que les clients soient redirigés vers la page de connexion pour toute action de repos envoyée sans jeton d'autorisation. Comme toutes mes actions de repos sont basées sur Ajax, j'avais besoin d'un bon moyen générique pour rediriger vers la page de connexion au lieu de gérer la fonction de succès Ajax.

C'est ce que j'ai fait:

Dans toute requête Ajax, mon serveur retournera une réponse JSON 200 "BESOIN D'AUTHENTIFIER" (si le client doit s'authentifier).

Exemple simple en Java (côté serveur):

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

Dans mon Javascript, j'ai ajouté le code suivant:

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

Et c'est à peu près tout.


Essayer

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

Mettez-le sur la page de connexion. S'il a été chargé dans un div sur la page principale, il redirigera jusqu'à la page de connexion. "#site" est un identifiant d'un div qui se trouve sur toutes les pages sauf la page de connexion.


J'ai lu cette question et mis en œuvre l'approche qui a été énoncée concernant la définition du code d'état de la réponse à 278 afin d'éviter que le navigateur gère les redirections de manière transparente. Même si cela a fonctionné, j'étais un peu insatisfait car c'est un peu un hack.

Après plus de fouilles, j'ai abandonné cette approche et j'ai utilisé JSON . Dans ce cas, toutes les réponses aux requêtes ajax ont le code d'état 200 et le corps de la réponse contient un objet JSON qui est construit sur le serveur. Le javascript sur le client peut ensuite utiliser l'objet JSON pour décider de ce qu'il doit faire.

J'ai eu un problème similaire au vôtre. J'effectue une requête ajax qui a 2 réponses possibles: une qui redirige le navigateur vers une nouvelle page et une qui remplace un formulaire HTML existant sur la page actuelle avec une nouvelle. Le code de jquery pour faire ceci ressemble à ceci:

$.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'objet JSON "data" est construit sur le serveur pour avoir 2 membres: data.redirect et data.form. J'ai trouvé cette approche beaucoup mieux.


J'ai résolu ce problème par:

  1. Ajouter un en-tête personnalisé à la réponse:

    public ActionResult Index(){
        if (!HttpContext.User.Identity.IsAuthenticated)
        {
            HttpContext.Response.AddHeader("REQUIRES_AUTH","1");
        }
        return View();
    }
    
  2. Liaison d'une fonction JavaScript à l'événement ajaxSuccess et vérification pour voir si l'en-tête existe:

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

J'ai résolu cela en mettant le suivant dans ma page login.php.

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

J'aime la méthode de Timmerz avec une légère torsion de citron. Si vous recevez un message contentType de texte / html lorsque vous attendez JSON , vous êtes probablement redirigé. Dans mon cas, je recharge simplement la page, et elle est redirigée vers la page de connexion. Oh, et vérifiez que le statut jqXHR est 200, ce qui semble idiot, parce que vous êtes dans la fonction d'erreur, non? Sinon, des cas d'erreurs légitimes forceront un rechargement itératif (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();
    }

});

Je n'ai eu aucun succès avec la solution d'en-tête - ils n'ont jamais été repris dans ma méthode ajaxSuccess / ajaxComplete. J'ai utilisé la réponse de Steg avec la réponse personnalisée, mais j'ai modifié le côté JS certains. Je configure une méthode que j'appelle dans chaque fonction pour pouvoir utiliser les méthodes standard $.get et $.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);
    }
}

Exemple d'utilisation ...

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

Je sais que ce sujet est ancien, mais je vais donner une autre approche que j'ai trouvée et décrite précédemment. Fondamentalement, j'utilise ASP.MVC avec WIF (mais ce n'est pas vraiment important pour le contexte de ce sujet - la réponse est adéquate quels que soient les frameworks utilisés.) L'indice reste inchangé - traitant des problèmes liés aux échecs d'authentification lors des requêtes ajax ) .

L'approche ci-dessous peut être appliquée à toutes les requêtes ajax (si elles ne redéfinissent pas évidemment l'événement beforeSend).

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

Avant qu'une requête ajax ne soit effectuée, la méthode CheckPulse est invoquée (la méthode du contrôleur peut être la plus simple):

[Authorize]
public virtual void CheckPulse() {}

Si l'utilisateur n'est pas authentifié (le jeton a expiré), cette méthode n'est pas accessible (protégée par l'attribut Authorize ). Du fait que le cadre gère l'authentification, alors que le jeton expire, il met le statut http 302 à la réponse. Si vous ne voulez pas que votre navigateur traite la réponse 302 de manière transparente, attrapez-le dans Global.asax et changez l'état de la réponse - par exemple à 200 OK. En outre, ajoutez l'en-tête, ce qui vous demande de traiter cette réponse de façon particulière (plus tard du côté 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");
    }
}

Enfin, côté client, recherchez un tel en-tête personnalisé. Si présent, la redirection complète vers la page d'ouverture de session doit être effectuée (dans mon cas window.location est remplacé par url from request qui est géré automatiquement par my 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;
                }
            }
    });
}

Je voulais juste me connecter à n'importe quelle requête ajax pour la page entière. @ SuperG m'a fait démarrer. Voici ce que j'ai fini par:

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

Je voulais vérifier spécifiquement certains codes d'état http pour baser ma décision sur. Cependant, vous pouvez simplement lier à ajaxError pour obtenir autre chose que le succès (200 seulement peut-être?) J'aurais pu écrire:

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

La plupart des solutions données utilisent une solution de contournement, en utilisant un en-tête supplémentaire ou un code HTTP inappropié. Ces solutions fonctionneront probablement mais se sentiront un peu «hacky». J'ai trouvé une autre solution.

Nous utilisons WIF qui est configuré pour rediriger (passiveRedirectEnabled = "true") sur une réponse 401. La redirection est utile lors de la gestion de requêtes normales mais ne fonctionnera pas pour les requêtes AJAX (puisque les navigateurs n'exécuteront pas le 302 / redirect).

En utilisant le code suivant dans votre global.asax, vous pouvez désactiver la redirection pour les requêtes 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;
        }
    }

Cela vous permet de renvoyer 401 réponses pour les requêtes AJAX, que votre javascript peut ensuite gérer en rechargeant la page. Recharger la page lancera un 401 qui sera géré par WIF (et WIF redirigera l'utilisateur vers la page de connexion).

Un exemple de javascript pour gérer les erreurs 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);
    }
});

La solution qui a finalement été implémentée consistait à utiliser un wrapper pour la fonction de rappel de l'appel Ajax et, dans cette enveloppe, à vérifier l'existence d'un élément spécifique sur le bloc HTML renvoyé. Si l'élément a été trouvé, l'encapsuleur a exécuté une redirection. Si ce n'est pas le cas, l'encapsuleur a transféré l'appel à la fonction de rappel réelle.

Par exemple, notre fonction wrapper était quelque chose comme:

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

Ensuite, lors de l'appel Ajax, nous avons utilisé quelque chose comme:

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

Cela a fonctionné pour nous car tous les appels Ajax renvoyaient toujours du code HTML dans un élément DIV que nous utilisons pour remplacer une partie de la page. De plus, nous n'avions besoin que de rediriger vers la page de connexion.


Si vous souhaitez également transmettre les valeurs, vous pouvez également définir les variables de session et accéder à Eg: Dans votre jsp, vous pouvez écrire

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

Et puis vous pouvez stocker cette valeur de temp dans votre variable javascript et jouer


Une autre solution que j'ai trouvée (particulièrement utile si vous voulez définir un comportement global) est d'utiliser la $.ajaxsetup() avec la propriété statusCode . Comme d'autres l'ont souligné, n'utilisez pas de code d'état de redirection ( 3xx ), utilisez plutôt un 4xx état 4xx et gérez le côté client de redirection.

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

Remplacez 400 par le code d'état que vous voulez gérer. Comme déjà mentionné 401 Unauthorized pourrait être une bonne idée. J'utilise le 400 car il est très peu spécifique et je peux utiliser le 401 pour des cas plus spécifiques (comme les fausses informations de connexion). Ainsi, au lieu de rediriger directement votre backend, vous devez renvoyer un 4xx erreur 4xx lorsque la session a expiré et que vous avez géré le côté client de la redirection. Fonctionne parfaitement pour moi même avec des cadres comme backbone.js


dans le servlet, vous devez mettre response.setStatus(response.SC_MOVED_PERMANENTLY); pour envoyer le statut '301' xmlHttp dont vous avez besoin pour une redirection ...

et dans la fonction $ .ajax vous ne devriez pas utiliser la fonction .toString() ..., juste

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

le problème est qu'il n'est pas très flexible, vous ne pouvez pas décider où vous voulez rediriger ..

rediriger à travers les servlets devrait être le meilleur moyen. mais je ne peux toujours pas trouver la bonne façon de le faire.


Additionally you will probably want to redirect user to the given in headers URL. So finally it will looks like this:

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

UPD: Opps. Have the same task, but it not works. Doing this stuff. I'll show you solution when I'll find it.


I was having this problem on a django app I'm tinkering with (disclaimer: I'm tinkering to learn, and am in no way an expert). What I wanted to do was use jQuery ajax to send a DELETE request to a resource, delete it on the server side, then send a redirect back to (basically) the homepage. When I sent HttpResponseRedirect('/the-redirect/') from the python script, jQuery's ajax method was receiving 200 instead of 302. So, what I did was to send a response of 300 with:

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

Then I sent/handled the request on the client with jQuery.ajax like so:

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

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

Maybe using 300 isn't "right", but at least it worked just like I wanted it to.

PS :this was a huge pain to edit on the mobile version of SO. Stupid ISP put my service cancellation request through right when I was done with my answer!


You can also hook XMLHttpRequest send prototype. This will work for all sends (jQuery/dojo/etc) with one handler.

I wrote this code to handle a 500 page expired error, but it should work just as well to trap a 200 redirect. Ready the wikipedia entry on XMLHttpRequest onreadystatechange about the meaning of 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