Como gerenciar uma solicitação de redirecionamento após uma chamada do jQuery Ajax

1370

Estou usando $.post()para chamar um servlet usando Ajax e, em seguida, usando o fragmento HTML resultante para substituir um divelemento na página atual do usuário. No entanto, se a sessão atingir o tempo limite, o servidor envia uma diretiva de redirecionamento para enviar o usuário à página de logon. Nesse caso, o jQuery está substituindo o divelemento pelo conteúdo da página de login, forçando os olhos do usuário a testemunhar uma cena rara.

Como gerenciar uma diretiva de redirecionamento de uma chamada Ajax com o jQuery 1.2.6?

Elliot Vargas
fonte
1
(não é uma resposta como tal) - Eu fiz isso no passado editando a biblioteca jquery e adicionando uma verificação da página de login em cada XHR concluído. Não é a melhor solução, pois precisaria ser feita toda vez que você atualizar, mas resolve o problema.
Sugendran 14/10/08
1
Veja a pergunta relacionada: stackoverflow.com/questions/5941933/…
Nutel
O HttpContext.Response.AddHeadere check no sucesso do ajaxsetup é o caminho a seguir
LCJ
4
Por que o servidor não pode retornar 401? Nesse caso, você pode ter um $ .ajaxSetup global e usar o código de status para redirecionar a página.
Vishal
1
este link doanduyhai.wordpress.com/2012/04/21/... me dá a solução certa
pappu_kutty

Respostas:

697

Li essa pergunta e implementei a abordagem que foi declarada em relação à configuração do código de status HTTP da resposta como 278, a fim de evitar que o navegador manipule de maneira transparente os redirecionamentos. Embora isso tenha funcionado, fiquei um pouco insatisfeito, pois é um pouco complicado.

Após mais pesquisas, abandonei essa abordagem e usei o JSON . Nesse caso, todas as respostas às solicitações AJAX têm o código de status 200 e o corpo da resposta contém um objeto JSON que é construído no servidor. O JavaScript no cliente pode então usar o objeto JSON para decidir o que ele precisa fazer.

Eu tive um problema semelhante ao seu. Realizo uma solicitação AJAX com 2 respostas possíveis: uma que redireciona o navegador para uma nova página e outra que substitui um formulário HTML existente na página atual por um novo. O código jQuery para fazer isso se parece com:

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

O objeto JSON "data" é construído no servidor para ter 2 membros: data.redirecte data.form. Eu achei essa abordagem muito melhor.

Steg
fonte
58
Conforme declarado na solução em stackoverflow.com/questions/503093/… , é melhor usar window.location.replace (data.redirect); que window.location.href = data.redirect;
Carles Barrobés
8
Qualquer que seja o motivo, não seria melhor usar códigos HTTP na ação. Por exemplo, um código 307 que é redirecionamento temporário HTTP?
Sergei Golos
15
@Sergei Golos, o motivo é que, se você faz um redirecionamento HTTP, o redirecionamento nunca chega ao retorno de chamada de sucesso do ajax. O navegador processa o redirecionamento, fornecendo um código 200 com o conteúdo do destino do redirecionamento.
Miguel Silva
2
como seria o código do servidor? para ter um valor de retorno data.form e data.redirect. basicamente como eu determino se eu coloco um redirecionamento nele?
Vnge
6
Essa resposta seria mais útil se tivesse mostrado como isso é feito no servidor.
Pedro Hoehl Carvalho 22/10/2015
243

Resolvi esse problema:

  1. Incluindo um cabeçalho personalizado na resposta:

    public ActionResult Index(){
        if (!HttpContext.User.Identity.IsAuthenticated)
        {
            HttpContext.Response.AddHeader("REQUIRES_AUTH","1");
        }
        return View();
    }
  2. Vinculando uma função JavaScript ao ajaxSuccessevento e verificando se o cabeçalho existe:

    $(document).ajaxSuccess(function(event, request, settings) {
        if (request.getResponseHeader('REQUIRES_AUTH') === '1') {
           window.location = '/';
        }
    });
SuperG
fonte
6
Que solução incrível! Gosto da ideia de uma solução completa. Preciso verificar o status 403, mas posso usar a ligação ajaxSuccess no corpo para isso (que é o que eu realmente estava procurando). Obrigado.
Bretticus 04/08/10
4
Acabei de fazer isso e descobri que precisava do ajaxComplete onde estava usando a função $ .get () e qualquer status diferente de 200 não estava sendo acionado. Na verdade, eu provavelmente poderia ter ligado apenas ao ajaxError. Veja minha resposta abaixo para mais detalhes.
Bretticus 04/08/10
2
Tudo bem em muitos casos, mas e se sua estrutura manipular a autorização?
jwaliszko
13
Gosto da abordagem do cabeçalho, mas também acho - como @ mwoods79 - que o conhecimento de onde redirecionar não deve ser duplicado. Resolvi isso adicionando um cabeçalho REDIRECT_LOCATION em vez de um booleano.
Rintcius
3
Tome cuidado para definir o cabeçalho na resposta APÓS o redirecionamento. Conforme descrito em outras respostas nesta página, o redirecionamento pode ser transparente para o manipulador ajaxSucces. Assim, incluí o cabeçalho na resposta GET da página de logon (que acabou provocando apenas o ajaxSuccess no meu cenário).
sieppl
118

Nenhum navegador manipula as respostas 301 e 302 corretamente. E, de fato, o padrão até diz que eles devem lidar com eles "de forma transparente", o que é uma enorme dor de cabeça para os fornecedores da Biblioteca Ajax. No Ra-Ajax , fomos forçados a usar o código de status de resposta HTTP 278 (apenas alguns códigos de sucesso "não utilizados") para manipular redirecionamentos transparentes do servidor ...

Isso realmente me irrita, e se alguém aqui tem alguma "puxar" em W3C Eu apreciaria que você poderia deixar W3C saber que nós realmente precisamos para lidar com 301 e 302 códigos de nós mesmos ...! ;)

Thomas Hansen
fonte
3
Eu, por um movimento, que 278 deve se tornar parte das especificações HTTP oficiais.
precisa saber é o seguinte
2
Já não os trata de forma transparente? Se um recurso foi movido, manipulá-lo de forma transparente significa repetir a solicitação no URL fornecido. Isso é o que eu esperaria usando a API XMLHttpRequest.
Philippe Rathé 12/09
@ PhilippeRathé Concordo. Manuseio transparente é exatamente o que eu quero. E não sei por que é considerado ruim.
smwikipedia
@smwikipedia Para organizar um redirecionamento da marcação na seção principal sem redirecionar a página.
CMC
se alguém aqui tiver algum "puxão" no W3C, agradeceria que você pudesse informar ao W3C que realmente precisamos lidar com os códigos 301 e 302 ...! ;)
Tommy.Tang
100

A solução que acabou sendo implementada foi usar um wrapper para a função de retorno de chamada da chamada Ajax e, nesse wrapper, verifique a existência de um elemento específico no pedaço HTML retornado. Se o elemento foi encontrado, o wrapper executou um redirecionamento. Caso contrário, o wrapper encaminhará a chamada para a função de retorno de chamada real.

Por exemplo, nossa função de wrapper era algo como:

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

Então, ao fazer a chamada do Ajax, usamos algo como:

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

Isso funcionou para nós porque todas as chamadas do Ajax sempre retornavam HTML dentro de um elemento DIV que usamos para substituir um pedaço da página. Além disso, precisamos apenas redirecionar para a página de login.

Elliot Vargas
fonte
4
Observe que isso pode ser reduzido para a função cbWrapper (funct) {retornar função (dados) {if ($ ("# myForm", data) .size ()> 0) top.location.href = "login"; else funct (dados); }}. Você só precisa cbWrapper (myActualCB) ao chamar .post. Sim, o código em comentários é uma bagunça, mas deve-se notar :)
Simen Echholt
tamanho é depreciado para que você possa usar .length aqui no lugar de tamanho
Sunil
66

Gosto do método de Timmerz com um leve toque de limão. Se você receber um contentType de texto / html retornado quando espera JSON , provavelmente será redirecionado. No meu caso, simplesmente recarrego a página e ela é redirecionada para a página de login. Ah, e verifique se o status do jqXHR é 200, o que parece bobagem, porque você está na função de erro, certo? Caso contrário, os casos de erro legítimos forçarão uma 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();
    }

});
BrianY
fonte
1
muito obrigado Brian, sua resposta foi a melhor para o meu cenário, embora eu gostaria que houvesse uma verificação mais segura, como comparar para qual URL / página está redirecionando, em vez da verificação "tipo de conteúdo" simples. Não consegui encontrar para qual página está redirecionando do objeto jqXHR.
Johnny
Eu verifiquei o status 401 e depois redirecionei. Funciona como um campeão.
Eric
55

Use a $.ajax()chamada de baixo nível :

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

Tente isso para um redirecionamento:

if (xmlHttp.code != 200) {
  top.location.href = '/some/other/page';
}
Até
fonte
Eu estava tentando evitar as coisas de baixo nível. De qualquer forma, suponha que eu use algo parecido com o que você descreve, como forço o redirecionamento do navegador depois de detectar que o código HTTP é 3xx? Meu objetivo é redirecionar o usuário, não apenas para anunciar que sua sessão expirou.
Elliot Vargas
4
Btw, $ .ajax () não é muito, muito baixo nível. É apenas de baixo nível em termos de jQuery, porque existem $ .get, $ .post, etc., que são muito mais simples que $ .ajax e todas as suas opções.
Até
16
Oh garoto! Desculpe, tive que "não aceitar" sua resposta, ainda é muito útil. O problema é que os redirecionamentos são gerenciados automaticamente pelo XMLHttpRequest, portanto, SEMPRE recebo um código de status 200 após o redirecionamento (suspiro!). Acho que vou ter que fazer algo desagradável, como analisar o HTML e procurar um marcador.
Elliot Vargas
1
Apenas curioso, se a sessão termina no servidor, isso não significa que o servidor envia um SESSIONID diferente? não poderíamos simplesmente detectar isso?
precisa
10
NOTA: Isso não funciona para redirecionamentos. o ajax irá para a nova página e retornará seu código de status.
33

Eu só queria compartilhar minha abordagem, pois isso pode ajudar alguém:

Basicamente, incluí um módulo JavaScript que lida com as coisas de autenticação, como exibir o nome de usuário e, neste caso, manipular o redirecionamento para a página de login .

Meu cenário: Basicamente, temos um servidor ISA entre o qual ouve todas as solicitações e responde com um cabeçalho 302 e um local na nossa página de login.

No meu módulo JavaScript, minha abordagem inicial foi algo como

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

O problema (como muitos aqui já mencionados) é que o navegador lida com o redirecionamento por si só, portanto, meu ajaxCompleteretorno de chamada nunca foi chamado, mas recebi a resposta da página de login já redirecionada, que obviamente era umastatus 200 . O problema: como você detecta se a resposta 200 bem-sucedida é sua página de login real ou apenas alguma outra página arbitrária?

A solução

Como não consegui capturar 302 respostas de redirecionamento, adicionei um LoginPagecabeçalho na minha página de login que continha o URL da própria página de login. No módulo, agora escuto o cabeçalho e faço um redirecionamento:

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

... e isso funciona como charme :). Você pode se perguntar por que eu incluo o URL no LoginPagecabeçalho ... bem, basicamente porque não encontrei uma maneira de determinar o URL GETresultante do redirecionamento automático de localização do xhrobjeto ...

Juri
fonte
+1 - mas cabeçalhos personalizados devem começar com X-um cabeçalho melhor para usar X-LoginPage: http://example.com/login.
uınbɐɥs
6
@ShaquinTrifonoff Não existe mais. Eu não usei o prefixo X porque em junho de 2011 um documento da ITEF propôs sua depreciação e, de fato, com junho de 2012 não é oficial que os cabeçalhos personalizados não devam mais ser prefixados X-.
Juri
Também temos um servidor ISA e acabei de encontrar o mesmo problema. Em vez de contornar isso no código, usamos as instruções em kb2596444 para configurar o ISA para parar o redirecionamento.
scott pedra
29

Sei que esse tópico é antigo, mas darei outra abordagem que encontrei e descrevi anteriormente aqui . Basicamente, estou usando o ASP.MVC com WIF (mas isso não é realmente importante para o contexto deste tópico - a resposta é adequada, independentemente de quais estruturas sejam usadas. A pista permanece inalterada - lidando com problemas relacionados a falhas de autenticação durante a execução de solicitações de ajax ) .

A abordagem mostrada abaixo pode ser aplicada a todas as solicitações de ajax prontas para uso (se elas não redefinirem antes do evento SendS obviamente).

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

Antes de qualquer solicitação ajax ser executada, o CheckPulsemétodo é chamado (o método do controlador, que pode ser qualquer coisa mais simples):

[Authorize]
public virtual void CheckPulse() {}

Se o usuário não estiver autenticado (o token expirou), esse método não poderá ser acessado (protegido pelo Authorizeatributo). Como a estrutura lida com autenticação, enquanto o token expira, coloca o status http 302 na resposta. Se você não deseja que seu navegador lide com a resposta 302 de forma transparente, pegue-o no Global.asax e altere o status da resposta - por exemplo, para 200 OK. Além disso, adicione o cabeçalho, que instrui você a processar essa resposta de maneira especial (posteriormente no lado do 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, no lado do cliente, verifique esse cabeçalho personalizado. Se presente - o redirecionamento completo para a página de logon deve ser feito (no meu caso, window.locationé substituído por url da solicitação, que é tratada automaticamente pela minha estrutura).

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
fonte
Corrigi o problema usando o evento PostAuthenticateRequest em vez do evento EndRequest.
Frinavale 28/10
@JaroslawWaliszko Colei o evento errado na minha última resposta! Eu quis dizer o evento PreSendRequestHeaders ..... não PostAuthenticateRequest! >> blush << obrigado por apontar meu erro.
Frinavale 28/10
@JaroslawWaliszko ao usar o WIF, você também pode retornar 401 respostas para solicitações AJAX e deixar seu javascript lidar com elas. Além disso, você está assumindo que todos os 302 requerem autenticação, o que pode não ser verdade em todos os casos. Adicionei uma resposta se alguém estiver interessado.
Rob
26

Eu acho que a melhor maneira de lidar com isso é aproveitar os códigos de resposta do protocolo HTTP existentes, especificamente 401 Unauthorized.

Aqui está como eu resolvi:

  1. Lado do servidor: se a sessão expirar e a solicitação for ajax. envie um cabeçalho de código de resposta 401
  2. Lado do cliente: vincular-se aos eventos do 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';
    }
    });

Na IMO, isso é mais genérico e você não está escrevendo algumas novas especificações / cabeçalho personalizados. Você também não deve modificar nenhuma das chamadas ajax existentes.

Edit: Pelo comentário de @ Rob abaixo, 401 (o código de status HTTP para erros de autenticação) deve ser o indicador. Consulte 403 Respostas HTTP proibidas vs 401 não autorizadas para obter mais detalhes. Com isso dito, algumas estruturas da Web usam 403 para erros de autenticação e autorização - portanto, adapte-se de acordo. Obrigado Rob.

rynop
fonte
Eu uso a mesma abordagem. O jQuery realmente chama o código de erro ajaxSuccess on 403? Eu acho que parte única ajaxError é realmente necessário
Marius Balčytis
24

Resolvi esse problema da seguinte maneira:

Adicione um middleware para processar a resposta; se for um redirecionamento para uma solicitação ajax, altere a resposta para uma resposta normal com o URL de redirecionamento.

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

Em ajaxComplete, se a resposta contiver redirecionamento, ele deverá ser um redirecionamento; portanto, altere o local do 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;
       }
   }
}
Tyr
fonte
20

Eu tenho uma solução simples que funciona para mim, não é necessário alterar o código do servidor ... basta adicionar uma colher de chá de noz-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 a presença da tag html, mas você pode alterar o indexOf para procurar por qualquer string exclusiva que exista na sua página de login ...

Timmerz
fonte
Isso não parece funcionar para mim, continua chamando a função definida com a chamada ajax, é como se não estivesse substituindo o método de sucesso.
adriaanp
Parece que não funciona para mim, pelo menos mais. Possível explicação aqui: stackoverflow.com/a/12010724/260665
Raj Pawan Gumdal
20

Outra solução que eu encontrei (especialmente útil se você quiser definir um comportamento global) é usar o $.ajaxsetup()método em conjunto com a statusCodepropriedade . Como outros apontaram, não use um código de status de redirecionamento ( 3xx), use um 4xxcódigo de status e lide com o redirecionamento do lado do cliente.

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

Substitua 400pelo código de status que você deseja manipular. Como já mencionado, 401 Unauthorizedpode ser uma boa ideia. Uso o 400dado que é muito inespecífico e posso usá-lo 401para casos mais específicos (como credenciais de logon incorretas). Portanto, em vez de redirecionar diretamente, o back-end deve retornar um 4xxcódigo de erro quando a sessão expirar e você lida com o redirecionamento do lado do cliente. Funciona perfeito para mim, mesmo com estruturas como o backbone.js

morten.c
fonte
Onde mencionar a função na página?
Vikrant
@ Vikrant Se eu entendi sua pergunta corretamente, você pode chamar a função logo após o jQuery ser carregado e antes de fazer suas solicitações reais.
morten.c
19

A maioria das soluções fornecidas usa uma solução alternativa, usando um cabeçalho extra ou um código HTTP inapropriado. Essas soluções provavelmente funcionarão, mas parecerão um pouco "hacky". Eu vim com outra solução.

Estamos usando o WIF configurado para redirecionar (passiveRedirectEnabled = "true") em uma resposta 401. O redirecionamento é útil ao lidar com solicitações normais, mas não funciona para solicitações AJAX (já que os navegadores não executam o 302 / redirecionamento).

Usando o código a seguir no seu global.asax, você pode desativar o redirecionamento para solicitações 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;
        }
    }

Isso permite que você retorne 401 respostas para solicitações AJAX, que seu javascript pode manipular recarregando a página. Recarregar a página lançará um 401, que será tratado pelo WIF (e o WIF redirecionará o usuário para a página de login).

Um exemplo de javascript para manipular erros 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);
    }
});
Roubar
fonte
Boa solução. Obrigado.
DanMan
19

Esse problema pode aparecer em seguida, usando o método ASP.NET MVC RedirectToAction. Para impedir que o formulário exiba a resposta em div, você pode simplesmente fazer algum tipo de filtro de resposta ajax para respostas recebidas com $ .ajaxSetup . Se a resposta contiver redirecionamento MVC, você poderá avaliar esta expressão no lado JS. Exemplo de código para JS abaixo:

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

Se os dados forem: "window.location = '/ Acount / Login'" acima do filtro, será capturado e avaliado para fazer o redirecionamento em vez de permitir que os dados sejam exibidos.

Przemek Marcinkiewicz
fonte
dataestá no corpo ou no cabeçalho da resposta?
GMsoF
16

Reunindo o que Vladimir Prudnikov e Thomas Hansen disseram:

  • Altere o código do servidor para detectar se é um XHR. Se estiver, defina o código de resposta do redirecionamento como 278. No django:
   if request.is_ajax():
      response.status_code = 278

Isso faz com que o navegador trate a resposta como um sucesso e entregue-a ao seu Javascript.

  • Em seu JS, verifique se o envio do formulário é via Ajax, verifique o código de resposta e redirecione, se necessário:
$('#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
fonte
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
fonte
12

Tentar

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

Coloque-o na página de login. Se ele foi carregado em uma div na página principal, será redirecionado até a página de login. "#site" é um ID de uma div localizada em todas as páginas, exceto na página de login.

podeig
fonte
12

Embora as respostas pareçam funcionar para as pessoas, se você estiver usando o Spring Security, achei a extensão do LoginUrlAuthenticationEntryPoint e a adição de código específico para lidar com o AJAX de maneira mais robusta. A maioria dos exemplos intercepta todos os redirecionamentos, não apenas falhas de autenticação. Isso era indesejável para o projeto em que trabalho. Talvez seja necessário estender o ExceptionTranslationFilter e substituir o método "sendStartAuthentication" para remover a etapa de armazenamento em cache se você não desejar que a solicitação AJAX com falha seja armazenada em cache.

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

Fontes: 1 , 2

John
fonte
3
Seria útil (para mim) se os eleitores com menos votos explicassem por que estão votando menos. Se houver algo ruim com esta solução, eu gostaria de aprender com meus erros. Obrigado.
John
Se estiver usando Spring e também usando JSF, verifique também: ("parcial / ajax"). EqualsIgnoreCase (request.getHeader ("faces-request"));
J liso
Os usuários podem ter votado negativamente porque você não mencionou: (1) os mods necessários do lado do cliente para detectar sua resposta de erro; (2) os mods necessários à configuração do Spring para adicionar seu LoginUrlAuthenticationEntryPoint personalizado à cadeia de filtros.
J liso
Sua resposta é semelhante à do @Arpad. Funcionou para mim; usando o Spring Security 3.2.9. stackoverflow.com/a/8426947/4505142
Darren Parker
11

Resolvi isso colocando o seguinte na minha página login.php.

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

Deixe-me apenas citar novamente o problema, conforme descrito por @Steg

Eu tive um problema semelhante ao seu. Realizo uma solicitação ajax com 2 respostas possíveis: uma que redireciona o navegador para uma nova página e outra que substitui um formulário HTML existente na página atual por um novo.

IMHO, este é um desafio real e terá que ser oficialmente estendido aos padrões HTTP atuais.

Acredito que o novo Http Standard será usar um novo código de status. significado: atualmente 301/302diz ao navegador para buscar o conteúdo desta solicitação em um novolocation .

No padrão estendido, ele dirá que, se a resposta status: 308(apenas um exemplo), o navegador deve redirecionar a página principal para o diretóriolocation fornecida.

Dito isto; Estou inclinado a imitar esse comportamento futuro e, portanto, quando um document.redirect é necessário, o servidor responde como:

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

Quando JS obtém o " status: 204", ele verifica a existência do x-status: 308cabeçalho e faz um document.redirect para a página fornecida no locationcabeçalho.

Isso faz algum sentido para você?

Chaim Klar
fonte
7

Alguns podem achar o útil abaixo:

Eu queria que os clientes fossem redirecionados para a página de login para qualquer ação de descanso enviada sem um token de autorização. Como todas as minhas ações restantes são baseadas no Ajax, eu precisava de uma boa maneira genérica de redirecionar para a página de login, em vez de manipular a função de sucesso do Ajax.

Isto é o que eu fiz:

Em qualquer solicitação do Ajax, meu servidor retornará uma resposta do Json 200 "PRECISA AUTENTICAR" (se o cliente precisar se autenticar).

Exemplo simples em Java (lado do 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());
        }
    }
}

No meu Javascript, adicionei o seguinte 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);
        }
    };      
});

E é sobre isso.

Tomer
fonte
5

no servlet que você deve colocar response.setStatus(response.SC_MOVED_PERMANENTLY); para enviar o status xmlHttp '301' necessário para um redirecionamento ...

e na função $ .ajax você não deve usar a .toString()função ..., apenas

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

o problema é que não é muito flexível, você não pode decidir para onde deseja redirecionar.

o redirecionamento através dos servlets deve ser o melhor caminho. mas ainda não consigo encontrar o caminho certo para fazê-lo.


fonte
5

Eu só queria me apegar a quaisquer solicitações de ajax para a página inteira. @SuperG me começou. Aqui está o que eu acabei com:

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

Eu queria verificar especificamente determinados códigos de status http para basear minha decisão. No entanto, você pode simplesmente associar-se ao ajaxError para obter algo que não seja sucesso (talvez apenas 200?). Eu poderia ter escrito:

$('body').bind('ajaxError', function(event,request,settings){
    window.location.replace("http://mysite.tld/login");
}
Bretticus
fonte
1
esta se esconder quaisquer outros erros que fazem problemático solução de problemas
Tim Abell
1
Um 403 não significa que o usuário não está autenticado, significa que o usuário (provavelmente autenticado) não tem permissão para exibir o recurso solicitado. Portanto, não deve redirecionar para a página de login
Rob
5

Se você também deseja passar os valores, também pode definir as variáveis ​​da sessão e acessar, por exemplo: No jsp, você pode escrever

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

E então você pode armazenar esse valor temporário na sua variável javascript e brincar

karthik339
fonte
5

Não tive sucesso com a solução do cabeçalho - eles nunca foram detectados no meu método ajaxSuccess / ajaxComplete. Usei a resposta de Steg com a resposta personalizada, mas modifiquei algumas das JS. Eu configurei um método que chamo em cada função para poder usar o padrão $.gete os $.postmétodos.

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

Exemplo disso em 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;
}
jocull
fonte
5

Finalmente, resolvo o problema adicionando um costume HTTP Header. Logo antes da resposta para cada solicitação no lado do servidor, adiciono o URL solicitado atual ao cabeçalho da resposta.

Meu tipo de aplicativo no servidor é Asp.Net MVCe ele tem um bom lugar para fazer isso. em Global.asaxeu implementei o Application_EndRequestevento para:

    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 perfeito para mim! Agora, em todas as respostas do JQuery $.posti, temos os urlcabeçalhos de resposta solicitados e também outros que resultam do POSTmétodo por status 302,303 ....

e outra coisa importante é que não há necessidade de modificar o código no servidor nem no cliente.

e a próxima é a capacidade de obter acesso a outras informações da ação posterior, como erros, mensagens e ..., dessa maneira.

Eu postei isso, talvez ajude alguém :)

Ali Adlavaran
fonte
4

Eu estava tendo esse problema em um aplicativo de django com o qual estou mexendo (aviso: estou mexendo para aprender e não sou de forma alguma um especialista). O que eu queria fazer era usar o jQuery ajax para enviar uma solicitação DELETE para um recurso, excluí-lo no lado do servidor e enviar um redirecionamento de volta para (basicamente) a página inicial. Quando enviei a HttpResponseRedirect('/the-redirect/')partir do script python, o método ajax do jQuery estava recebendo 200 em vez de 302. Então, o que fiz foi enviar uma resposta 300 com:

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

Em seguida, enviei / processei a solicitação no cliente com jQuery.ajax da seguinte forma:

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

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

Talvez usar 300 não esteja "certo", mas pelo menos funcionou exatamente como eu queria.

PS: foi uma grande dor editar na versão móvel do SO. O ISP estúpido atendeu meu pedido de cancelamento de serviço quando terminei minha resposta!

Benny Jobigan
fonte
4

Você também pode conectar o protótipo de envio XMLHttpRequest. Isso funcionará para todos os envios (jQuery / dojo / etc) com um manipulador.

Eu escrevi esse código para lidar com um erro expirado de 500 páginas, mas deve funcionar da mesma maneira para interceptar um redirecionamento 200. Pronto a entrada da wikipedia no XMLHttpRequest ao alterar a estatística sobre o 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);
}
Curtis Yallop
fonte
2

Além disso, você provavelmente desejará redirecionar o usuário para o URL indicado nos cabeçalhos. Então, finalmente, ficará assim:

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

UPD: Opps. Tem a mesma tarefa, mas não funciona. Fazendo essas coisas. Mostrarei a você a solução quando a encontrar.

Vladimir Prudnikov
fonte
4
Essa resposta deve ser excluída pelo autor, pois ele próprio diz que isso não funciona. Ele pode postar uma solução funcional mais tarde. -1
TheCrazyProgrammer
A idéia é boa, mas o problema é que o cabeçalho "Location" nunca é passado.
Martin Zvarík 11/12/19
Minha edição foi rejeitada, então ... você precisa usar "var xmlHttp = $ .ajax ({...." a variável não pode estar dentro ... e use: console.log (xmlHttp.getAllResponseHeaders ())) ;
Martin Zvarík
2

Eu tenho uma solução de trabalho usando as respostas de @John e @Arpad link e @RobWinch ligação

Eu uso o Spring Security 3.2.9 e o jQuery 1.10.2.

Estenda a classe do Spring para causar resposta 4XX apenas de solicitações 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>

Nos meus JSPs, adicione um manipulador de erros AJAX global, como mostrado aqui

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

Além disso, remova os manipuladores de erro existentes das chamadas AJAX nas 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 ajude os outros.

Update1 Eu descobri que precisava adicionar a opção (sempre use-default-target = "true") à configuração de login do formulário. Isso foi necessário, pois depois que uma solicitação AJAX é redirecionada para a página de login (devido à sessão expirada), o Spring lembra a solicitação AJAX anterior e redireciona automaticamente para ela após o login. Isso faz com que o JSON retornado seja exibido na página do navegador. Claro, não é o que eu quero.

Atualização2 Em vez de usar always-use-default-target="true", use o @RobWinch exemplo de bloqueio de solicitações AJAX do requstCache. Isso permite que os links normais sejam redirecionados para o destino original após o login, mas o AJAX vai para a página inicial após o login.

Darren Parker
fonte