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

1370

J'utilise $.post()pour appeler un servlet en utilisant Ajax, puis en utilisant le fragment HTML résultant pour remplacer un divélément 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' divélément par le contenu de la page de connexion, forçant 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?

Elliot Vargas
la source
1
(pas une réponse en tant que telle) - J'ai fait cela dans le passé en modifiant la bibliothèque jquery et en ajoutant une vérification de la page de connexion sur chaque XHR complète. Ce n'est pas la meilleure solution car cela devrait être fait à chaque mise à niveau, mais cela résout le problème.
Sugendran
1
Voir la question connexe: stackoverflow.com/questions/5941933/…
Nutel
Le HttpContext.Response.AddHeadercontrôle et la réussite à ajaxsetup sont la voie à suivre
LCJ
4
Pourquoi le serveur ne peut-il pas retourner 401? Dans ce cas, vous pouvez avoir un $ .ajaxSetup global et utiliser le code d'état pour rediriger la page.
Vishal
1
ce lien doanduyhai.wordpress.com/2012/04/21/… me donne la bonne solution
pappu_kutty

Réponses:

697

J'ai lu cette question et mis en œuvre l'approche qui a été énoncée concernant la définition du code d'état HTTP de réponse à 278 afin d'éviter que le navigateur ne 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 avoir creusé davantage, j'ai abandonné cette approche et utilisé JSON . Dans ce cas, toutes les réponses aux demandes 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'exécute une demande 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 par un nouveau. Le code jQuery pour ce faire 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 "données" est construit sur le serveur pour avoir 2 membres: data.redirectet data.form. J'ai trouvé que cette approche était bien meilleure.

Steg
la source
58
Comme indiqué dans la solution de stackoverflow.com/questions/503093/…, il est préférable d'utiliser window.location.replace (data.redirect); que window.location.href = data.redirect;
Carles Barrobés
8
Quelle que soit la raison pour laquelle il ne serait pas préférable d'utiliser des codes HTTP sur l'action. Par exemple, un code 307 qui est une redirection temporaire HTTP?
Sergei Golos
15
@Sergei Golos, la raison est que si vous effectuez une redirection HTTP, la redirection n'arrive en fait jamais au rappel de succès ajax. Le navigateur traite la redirection en délivrant un code 200 avec le contenu de la destination de la redirection.
Miguel Silva
2
à quoi ressemblerait le code du serveur? pour avoir une valeur de retour de data.form et data.redirect. essentiellement comment puis-je déterminer si j'y mets une redirection?
Vnge
6
Cette réponse serait plus utile si elle avait montré comment cela se fait sur le serveur.
Pedro Hoehl Carvalho
244

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

  1. Ajout d'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. Lier une fonction JavaScript à l' ajaxSuccessévénement et vérifier si l'en-tête existe:

    $(document).ajaxSuccess(function(event, request, settings) {
        if (request.getResponseHeader('REQUIRES_AUTH') === '1') {
           window.location = '/';
        }
    });
SuperG
la source
6
Quelle solution géniale. J'aime l'idée d'une solution unique. Je dois vérifier l'état 403, mais je peux utiliser la liaison ajaxSuccess sur le corps pour cela (ce que je cherchais vraiment.) Merci.
Bretticus
4
Je viens de le faire et j'ai trouvé que j'avais besoin d'ajaxComplete où j'utilisais la fonction $ .get () et tout état autre que 200 ne se déclenche pas. En fait, j'aurais probablement pu me limiter à ajaxError à la place. Voir ma réponse ci-dessous pour plus de détails.
Bretticus
2
C'est correct dans de nombreux cas, mais que se passe-t-il si votre framework gère l'autorisation?
jwaliszko
13
J'aime l'approche en-tête, mais je pense aussi - comme @ mwoods79 - que la connaissance de la destination de la redirection ne doit pas être dupliquée. J'ai résolu cela en ajoutant un en-tête REDIRECT_LOCATION au lieu d'un booléen.
rintcius
3
Prenez soin de définir l'en-tête sur la réponse APRÈS la redirection. Comme décrit dans d'autres réponses sur cette page, la redirection peut être transparente pour le gestionnaire ajaxSucces. J'ai donc inclus l'en-tête sur la réponse GET de la page de connexion (qui a finalement et seulement déclenché ajaxSuccess dans mon scénario).
sieppl
118

Aucun navigateur ne gère correctement les réponses 301 et 302. Et en fait, la norme dit même qu'ils devraient les gérer "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 réussite "inutilisé") pour gérer les redirections transparentes depuis le serveur ...

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

Thomas Hansen
la source
3
Pour ma part, 278 devraient devenir en dehors de la spécification HTTP officielle.
Chris Marisic
2
Ne les gère-t-il pas déjà de manière transparente? Si une ressource a été déplacée, la gérer de manière transparente signifie répéter la demande sur l'URL fournie. C'est ce que j'attendrais en utilisant l'API XMLHttpRequest.
Philippe Rathé
@ PhilippeRathé D'accord. La manipulation transparente est exactement ce que je veux. Et je ne sais pas pourquoi c'est considéré comme mauvais.
smwikipedia
@smwikipedia Pour organiser une redirection du balisage dans la section principale sans rediriger la page.
cmc
si quelqu'un ici a du "pull" dans le W3C, j'apprécierais que vous puissiez faire savoir au W3C que nous avons vraiment besoin de gérer nous-mêmes les codes 301 et 302 ...! ;)
Tommy.Tang
100

La solution qui a finalement été implémentée était d'utiliser un wrapper pour la fonction de rappel de l'appel Ajax et dans ce wrapper de vérifier l'existence d'un élément spécifique sur le morceau HTML renvoyé. Si l'élément a été trouvé, le wrapper a exécuté une redirection. Sinon, 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 HTML dans un élément DIV que nous utilisons pour remplacer une partie de la page. De plus, nous avions seulement besoin de rediriger vers la page de connexion.

Elliot Vargas
la source
4
Notez que cela peut être raccourci en fonction cbWrapper (funct) {return function (data) {if ($ ("# myForm", data) .size ()> 0) top.location.href = "login"; else funct (données); }}. Vous n'avez alors besoin que de cbWrapper (myActualCB) lors de l'appel de .post. Oui, le code dans les commentaires est un gâchis mais il faut le noter :)
Simen Echholt
la taille est dépréciée, vous pouvez donc utiliser .length ici à la place de la taille
sunil
66

J'aime la méthode de Timmerz avec une légère touche de citron. Si vous obtenez un contentType de texte / html retourné lorsque vous attendez JSON , vous êtes très 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 l'état jqXHR est 200, ce qui semble idiot, car vous êtes dans la fonction d'erreur, non? Sinon, les cas d'erreur légitimes forceront un rechargement itératif (oups)

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

});
BrianY
la source
1
merci beaucoup Brian, votre réponse a été la meilleure pour mon scénario, même si j'aimerais qu'il y ait une vérification plus sûre, par exemple en comparant vers quelle URL / page redirige au lieu de la simple vérification de "type de contenu". Je n'ai pas pu trouver vers quelle page est redirigé depuis l'objet jqXHR.
Johnny
J'ai vérifié le statut 401, puis redirigé. Fonctionne comme un champion.
Eric
55

Utilisez l' $.ajax()appel de bas niveau :

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

Essayez ceci pour une redirection:

if (xmlHttp.code != 200) {
  top.location.href = '/some/other/page';
}
Jusqu'à
la source
J'essayais d'éviter les trucs de bas niveau. Quoi qu'il en soit, supposons que j'utilise quelque chose comme ce que vous décrivez, comment puis-je forcer une redirection de navigateur une fois que je détecte que le code HTTP est 3xx? Mon objectif est de rediriger l'utilisateur, pas seulement d'annoncer que sa session a expiré.
Elliot Vargas
4
Btw, $ .ajax () n'est pas très, très bas niveau. C'est juste de bas niveau en termes de jQuery car il y a $ .get, $ .post, etc. qui sont beaucoup plus simples que $ .ajax et toutes ses options.
jusqu'au
16
Oh mec! Désolé d'avoir dû "désaccepter" votre réponse, elle est toujours très utile. Le fait est que les redirections sont gérées automatiquement par XMLHttpRequest, donc j'obtiens TOUJOURS un code d'état 200 après la redirection (soupir!). Je pense que je devrai faire quelque chose de méchant comme analyser le HTML et chercher un marqueur.
Elliot Vargas
1
Juste curieux, si la session se termine sur le serveur, cela ne signifie-t-il pas que le serveur envoie un SESSIONID différent? ne pourrions-nous pas simplement détecter cela?
Salamander2007
10
REMARQUE: cela ne fonctionne pas pour les redirections. ajax ira à la nouvelle page et renverra son code d'état.
33

Je voulais juste partager mon approche car cela pourrait aider quelqu'un:

J'ai essentiellement inclus un module JavaScript qui gère les éléments d'authentification comme l'affichage du nom d'utilisateur et également ce cas, la gestion de la redirection vers la page de connexion .

Mon scénario: Nous avons essentiellement un serveur ISA entre les deux qui écoute toutes les demandes et répond avec un 302 et un en-tête d'emplacement à notre page de connexion.

Dans mon module JavaScript, mon approche initiale était quelque chose comme

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

Le problème (comme beaucoup ici déjà mentionné) est que le navigateur gère lui-même la redirection, c'est pourquoi mon ajaxCompleterappel n'a jamais été appelé, mais à la place, j'ai obtenu la réponse de la page de connexion déjà redirigée qui était évidemment un status 200. Le problème: comment détecter si la réponse 200 réussie est votre page de connexion réelle ou simplement une autre page arbitraire ??

La solution

Comme je n'ai pas pu capturer 302 réponses de redirection, j'ai ajouté un en- LoginPagetête sur ma page de connexion qui contenait l'url de la page de connexion elle-même. Dans le module, j'écoute maintenant l'en-tête et fais une redirection:

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

... et ça marche comme du charme :). Vous vous demandez peut-être pourquoi j'inclus l'URL dans l'en- LoginPagetête ... enfin, fondamentalement parce que je n'ai trouvé aucun moyen de déterminer l'URL GETrésultant de la redirection automatique de l'emplacement depuis l' xhrobjet ...

Juri
la source
+1 - mais les en-têtes personnalisés sont censés commencer par X-, donc un meilleur en-tête à utiliser serait X-LoginPage: http://example.com/login.
uınbɐɥs
6
@ShaquinTrifonoff Pas plus. Je n'ai pas utilisé le préfixe X parce qu'en juin 2011 un document ITEF a proposé leur dépréciation et en effet, avec juin 2012, il n'est pas officiel que les en-têtes personnalisés ne devraient plus être préfixés X-.
Juri
Nous avons également un serveur ISA et je viens de rencontrer le même problème. Plutôt que de le contourner dans le code, nous avons utilisé les instructions du kb2596444 pour configurer ISA pour arrêter la redirection.
scott stone
29

Je sais que ce sujet est ancien, mais je donnerai une autre approche que j'ai trouvée et décrite précédemment ici . 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 quel que soit le cadre utilisé. L'indice reste inchangé - traitant des problèmes liés aux échecs d'authentification lors de l'exécution de requêtes ajax ) .

L'approche illustrée ci-dessous peut être appliquée à toutes les demandes ajax prédéfinies (si elles ne sont pas redéfinies avant l'événement Send évidemment).

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

Avant toute requête ajax, la CheckPulseméthode est invoquée (la méthode du contrôleur qui 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 un Authorizeattribut). Étant donné que le framework gère l'authentification, tandis que le jeton expire, il place l'état http 302 dans la réponse. Si vous ne voulez pas que votre navigateur gère la réponse 302 de manière transparente, interceptez-la dans Global.asax et modifiez l'état de la réponse, par exemple 200 OK. De plus, ajoutez un en-tête, qui vous demande de traiter une telle réponse de manière spéciale (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, vérifiez cet en-tête personnalisé. Si présent - une redirection complète vers la page de connexion doit être effectuée (dans mon cas, window.locationest remplacée par l'URL de la demande qui est gérée automatiquement par mon 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;
                }
            }
    });
}
jwaliszko
la source
J'ai résolu le problème en utilisant l'événement PostAuthenticateRequest au lieu de l'événement EndRequest.
Frinavale
@JaroslawWaliszko J'ai collé le mauvais événement dans ma dernière réponse! Je voulais dire l'événement PreSendRequestHeaders ..... pas PostAuthenticateRequest! >> blush << merci d'avoir signalé mon erreur.
Frinavale
@JaroslawWaliszko lorsque vous utilisez WIF, vous pouvez également retourner 401 réponses pour les demandes AJAX et laisser votre javascript les gérer. Vous supposez également que tous les 302 nécessitent une authentification qui pourrait ne pas être vraie dans tous les cas. J'ai ajouté une réponse si quelqu'un est intéressé.
Rob
26

Je pense qu'une meilleure façon de gérer cela est de tirer parti des codes de réponse du protocole HTTP existants, en particulier 401 Unauthorized.

Voici comment je l'ai résolu:

  1. Côté serveur: si la session expire et que la demande est ajax. envoyer un en-tête de code de réponse 401
  2. Côté client: se lier aux événements 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';
    }
    });

OMI, c'est plus générique et vous n'écrivez pas de nouvelles spécifications / en-têtes personnalisés. Vous ne devriez pas non plus avoir à modifier aucun de vos appels ajax existants.

Edit: Selon le commentaire de @ Rob ci-dessous, 401 (le code d'état HTTP pour les erreurs d'authentification) devrait être l'indicateur. Voir 403 Interdit vs 401 Réponses HTTP non autorisées pour plus de détails. Cela étant dit, certains cadres Web utilisent 403 pour les erreurs d'authentification ET d'autorisation - alors adaptez-vous en conséquence. Merci Rob.

rynop
la source
J'utilise la même approche. JQuery appelle-t-il vraiment ajaxSuccess sur le code d'erreur 403? Je pense que seule une partie ajaxError est réellement nécessaire
Marius Balčytis
24

J'ai résolu ce problème comme ceci:

Ajoutez un middleware pour traiter la réponse, s'il s'agit d'une redirection pour une demande ajax, modifiez la réponse en une réponse normale avec l'URL de redirection.

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

Ensuite, dans ajaxComplete, si la réponse contient une redirection, il doit s'agir d'une redirection, alors changez l'emplacement du navigateur.

$('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;
       }
   }
}
Tyr
la source
20

J'ai une solution simple qui fonctionne pour moi, aucun changement de code de serveur nécessaire ... il suffit d'ajouter une cuillère à café de muscade ...

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

Je vérifie la présence de la balise html, mais vous pouvez modifier l'indexOf pour rechercher la chaîne unique qui existe dans votre page de connexion ...

Timmerz
la source
Cela ne semble pas fonctionner pour moi, cela continue d'appeler la fonction définie avec l'appel ajax, c'est comme si elle ne surchargeait pas la méthode du succès.
adriaanp
Cela ne semble pas fonctionner pour moi, du moins plus. Explication possible ici: stackoverflow.com/a/12010724/260665
Raj Pawan Gumdal
20

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

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

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

morten.c
la source
Où mentionner la fonction sur la page?
Vikrant
@Vikrant Si je comprends bien votre question, vous pouvez appeler la fonction juste après le chargement de jQuery et avant de faire vos demandes réelles.
morten.c
19

La plupart des solutions données utilisent une solution de contournement, en utilisant un en-tête supplémentaire ou un code HTTP inapproprié. Ces solutions fonctionneront très 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 du traitement des requêtes normales mais ne fonctionnera pas pour les requêtes AJAX (puisque les navigateurs n'exécuteront pas la redirection 302 /).

En utilisant le code suivant dans votre global.asax, vous pouvez désactiver la redirection pour les demandes 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 demandes AJAX, que votre javascript peut ensuite gérer en rechargeant la page. Le rechargement de 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);
    }
});
Rob
la source
Bonne solution. Merci.
DanMan
19

Ce problème peut apparaître lors de l'utilisation de la méthode ASP.NET MVC RedirectToAction. Pour empêcher le formulaire d'afficher la réponse en div, vous pouvez simplement faire une sorte de filtre de réponse ajax pour les réponses entrantes avec $ .ajaxSetup . Si la réponse contient une redirection MVC, vous pouvez évaluer cette expression du côté JS. Exemple de code pour JS ci-dessous:

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

Si les données sont: "window.location = '/ Acount / Login'" ci-dessus, le filtre interceptera et évaluera pour effectuer la redirection au lieu de laisser les données à afficher.

Przemek Marcinkiewicz
la source
datase trouve dans le corps ou l'en-tête de la réponse?
GMsoF
16

Réunissant ce que Vladimir Prudnikov et Thomas Hansen ont dit:

  • Modifiez votre code côté serveur pour détecter s'il s'agit d'un XHR. Si c'est le cas, définissez le code de réponse de la redirection sur 278. Dans django:
   if request.is_ajax():
      response.status_code = 278

Cela fait que le navigateur traite la réponse comme un succès et la transmet à votre Javascript.

  • Dans votre JS, assurez-vous que la soumission du formulaire se fait via Ajax, vérifiez le code de réponse et redirigez si nécessaire:
$('#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); 
});
Graham King
la source
13
    <script>
    function showValues() {
        var str = $("form").serialize();
        $.post('loginUser.html', 
        str,
        function(responseText, responseStatus, responseXML){
            if(responseStatus=="success"){
                window.location= "adminIndex.html";
            }
        });     
    }
</script>
Priyanka
la source
12

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 sera redirigé jusqu'à la page de connexion. "#site" est un identifiant de div qui se trouve sur toutes les pages sauf la page de connexion.

podeig
la source
12

Bien que les réponses semblent fonctionner pour les gens si vous utilisez Spring Security, j'ai trouvé l'extension de 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 voulez pas que la demande AJAX en échec 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

John
la source
3
Il serait utile (pour moi) que les électeurs en baisse expliquent pourquoi ils votent en bas. S'il y a quelque chose de mal avec cette solution, je voudrais apprendre de mes erreurs. Merci.
John
Si vous utilisez Spring et JSF, vérifiez également ceci: ("partial / ajax"). EqualsIgnoreCase (request.getHeader ("faces-request"));
J Slick
Les utilisateurs ont peut-être voté contre parce que vous n'avez pas mentionné: (1) les mods côté client requis pour détecter votre réponse d'erreur; (2) les mods requis à la configuration Spring pour ajouter votre LoginUrlAuthenticationEntryPoint personnalisé à la chaîne de filtrage.
J Slick
Votre réponse est similaire à celle de @Arpad. Cela a fonctionné pour moi; en utilisant Spring Security 3.2.9. stackoverflow.com/a/8426947/4505142
Darren Parker
11

J'ai résolu cela en mettant ce qui suit dans ma page login.php.

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

Permettez-moi de citer à nouveau le problème décrit par @Steg

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 par un nouveau.

À mon humble avis, il s'agit d'un véritable défi et devra être officiellement étendu aux normes HTTP actuelles.

Je crois que la nouvelle norme Http sera d'utiliser un nouveau code d'état. Signification: 301/302indique actuellement au navigateur d'aller chercher le contenu de cette demande dans une nouvelle location.

Dans le standard étendu, il dira que si la réponse status: 308(juste un exemple), alors le navigateur devrait rediriger la page principale vers la locationfournie.

Cela étant dit; Je suis enclin à imiter déjà ce comportement futur , et donc quand un document.redirect est nécessaire, je demande au serveur de répondre comme:

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

Lorsque JS obtient le " status: 204", il vérifie l'existence de l'en- x-status: 308tête et fait un document.redirect à la page fournie dans l'en- locationtête.

Est-ce que cela a un sens pour vous?

Chaim Klar
la source
7

Certains pourraient trouver les informations ci-dessous utiles:

Je voulais que les clients soient redirigés vers la page de connexion pour toute action de repos envoyée sans jeton d'autorisation. Étant donné que 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 réussite Ajax.

Voici ce que j'ai fait:

Sur toute demande Ajax, mon serveur retournera une réponse Json 200 "BESOIN D'AUTHENTIFIER" (si le client a besoin de 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 tout.

Tomer
la source
5

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

et dans la fonction $ .ajax vous ne devez pas utiliser la .toString()fonction ..., 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 souhaitez rediriger ..

la redirection via les servlets devrait être le meilleur moyen. mais je ne trouve toujours pas la bonne façon de le faire.


la source
5

Je voulais juste m'accrocher à toutes les demandes ajax pour la page entière. @SuperG m'a fait démarrer. Voici ce que j'ai fini avec:

// 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 de statut http sur lesquels baser ma décision. Cependant, vous pouvez simplement vous lier à ajaxError pour obtenir autre chose que du succès (200 seulement peut-être?) J'aurais pu écrire:

$('body').bind('ajaxError', function(event,request,settings){
    window.location.replace("http://mysite.tld/login");
}
Bretticus
la source
1
ce dernier cacherait toute autre erreur rendant le dépannage problématique
Tim Abell
1
Un 403 ne signifie pas que l'utilisateur n'est pas authentifié, cela signifie que l'utilisateur (probablement authentifié) n'a pas l'autorisation d'afficher la ressource demandée. Il ne doit donc pas rediriger vers la page de connexion
Rob
5

Si vous souhaitez également transmettre les valeurs, vous pouvez également définir les variables de session et accéder par exemple: 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 temporaire dans votre variable javascript et jouer

karthik339
la source
5

Je n'ai eu aucun succès avec la solution d'en-tête - ils n'ont jamais été récupérés dans ma méthode ajaxSuccess / ajaxComplete. J'ai utilisé la réponse de Steg avec la réponse personnalisée, mais j'ai modifié certains côté JS. J'ai configuré une méthode que j'appelle dans chaque fonction afin que je puisse utiliser les méthodes $.getet standards $.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;
}
jocull
la source
5

Enfin, je résous le problème en ajoutant une coutume HTTP Header. Juste avant la réponse pour chaque demande côté serveur, j'ajoute l'URL actuellement demandée à l'en-tête de la réponse.

Mon type d'application sur le serveur est Asp.Net MVC, et il a un bon endroit pour le faire. en Global.asaxj'ai implémenté l' Application_EndRequestévénement donc:

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

    }

Cela fonctionne parfaitement pour moi! Maintenant , dans chaque réponse du JQuery $.postj'ai demandé les en- urltêtes de réponse et aussi d' autres qui vient à la suite de la POSTméthode par le statut 302, 303....

et une autre chose importante est qu'il n'est pas nécessaire de modifier le code côté serveur ou côté client.

et le suivant est la possibilité d'accéder aux autres informations de post-action telles que les erreurs, les messages et ..., de cette façon.

J'ai posté ça, peut-être aider quelqu'un :)

Ali Adlavaran
la source
4

J'avais ce problème sur une application django avec laquelle je bricolais (avertissement: je bricole pour apprendre, et je ne suis en aucun cas un expert). Ce que je voulais faire, c'était utiliser jQuery ajax pour envoyer une demande DELETE à une ressource, la supprimer côté serveur, puis renvoyer une redirection vers (essentiellement) la page d'accueil. Quand j'ai envoyé à HttpResponseRedirect('/the-redirect/')partir du script python, la méthode ajax de jQuery recevait 200 au lieu de 302. Donc, ce que j'ai fait était d'envoyer une réponse de 300 avec:

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

Ensuite, j'ai envoyé / traité la demande sur le client avec jQuery.ajax comme ceci:

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

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

Peut-être que l'utilisation de 300 n'est pas "correcte", mais au moins cela a fonctionné comme je le voulais.

PS: ce fut une énorme douleur à éditer sur la version mobile de SO. Stupide FAI a traité ma demande d'annulation de service juste après avoir fini avec ma réponse!

Benny Jobigan
la source
4

Vous pouvez également accrocher le prototype d'envoi XMLHttpRequest. Cela fonctionnera pour tous les envois (jQuery / dojo / etc) avec un gestionnaire.

J'ai écrit ce code pour gérer une erreur expirée de 500 pages, mais cela devrait aussi bien fonctionner pour intercepter une redirection 200. Préparez l'entrée wikipedia sur XMLHttpRequest onreadystatechange sur la signification 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);
}
Curtis Yallop
la source
2

De plus, vous souhaiterez probablement rediriger l'utilisateur vers l'URL indiquée dans les en-têtes. Donc, finalement, cela ressemblera à ceci:

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

UPD: Opps. Ont la même tâche, mais cela ne fonctionne pas. Faire ce genre de choses. Je vous montrerai une solution quand je la trouverai.

Vladimir Prudnikov
la source
4
Cette réponse devrait être supprimée par l'auteur car il dit lui-même que cela ne fonctionne pas. Il peut poster une solution de travail plus tard. -1
TheCrazyProgrammer
L'idée est bonne, mais le problème est que l'en-tête "Location" n'est jamais transmis.
Martin Zvarík
Ma modification a été rejetée, donc ... vous devez utiliser "var xmlHttp = $ .ajax ({...." la variable ne peut pas être à l'intérieur ... puis utiliser: console.log (xmlHttp.getAllResponseHeaders ()) ;
Martin Zvarík
2

Je suis un solulion de travail en utilisant les réponses de @John et @Arpad lien et @RobWinch lien

J'utilise Spring Security 3.2.9 et jQuery 1.10.2.

Étendez la classe de Spring pour provoquer une réponse 4XX uniquement à partir des requêtes 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>

Dans mes JSP, ajoutez un gestionnaire d'erreur AJAX global comme indiqué ici

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

Supprimez également les gestionnaires d'erreurs existants des appels AJAX dans les pages 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");
//            }
        });

J'espère que cela aide les autres.

Update1 J'ai trouvé que je devais ajouter l'option (always-use-default-target = "true") à la configuration de connexion au formulaire. Cela était nécessaire car après qu'une demande AJAX a été redirigée vers la page de connexion (en raison de la session expirée), Spring se souvient de la demande AJAX précédente et y redirige automatiquement après la connexion. Cela provoque l'affichage du JSON renvoyé sur la page du navigateur. Bien sûr, pas ce que je veux.

Update2 Au lieu d'utiliser always-use-default-target="true", utilisez l'exemple @RobWinch de blocage des requêtes AJAX à partir du requstCache. Cela permet aux liens normaux d'être redirigés vers leur cible d'origine après la connexion, mais AJAX accède à la page d'accueil après la connexion.

Darren Parker
la source